<template>
	<div class="personal-schedule-dialog">
		<!-- 弹窗遮罩 -->
		<div v-if="visible" class="dialog-overlay" @click="handleClose">
			<div class="dialog-content" @click.stop>
				<!-- 弹窗头部 -->
				<div class="dialog-header">
					<h3>个人排班</h3>
					<button @click="handleClose" class="close-btn">✕</button>
				</div>

				<!-- 弹窗主体 -->
				<div class="dialog-body">
					<!-- 左侧个人信息 -->
					<div class="personal-info-section">
						<!-- 骨架屏：个人信息加载状态 -->
						<el-skeleton v-if="loading" :rows="8" animated>
							<template #template>
								<!-- 个人头像和基本信息骨架 -->
								<div class="person-card">
									<div class="person-avatar">
										<el-skeleton-item variant="circle" style="width: 60px; height: 60px" />
									</div>
									<div class="person-details" style="margin-left: 16px; flex: 1">
										<el-skeleton-item variant="h3" style="width: 120px; margin-bottom: 8px" />
										<el-skeleton-item variant="text" style="width: 100px" />
									</div>
								</div>

								<!-- 月度统计骨架 -->
								<div class="monthly-stats" style="margin-top: 24px">
									<el-skeleton-item variant="h3" style="width: 140px; margin-bottom: 16px" />
									<div class="stats-grid">
										<div class="stat-item">
											<el-skeleton-item variant="text" style="width: 60px; margin-bottom: 4px" />
											<el-skeleton-item variant="text" style="width: 30px" />
										</div>
										<div class="stat-item">
											<el-skeleton-item variant="text" style="width: 70px; margin-bottom: 4px" />
											<el-skeleton-item variant="text" style="width: 30px" />
										</div>
										<div class="stat-item">
											<el-skeleton-item variant="text" style="width: 50px; margin-bottom: 4px" />
											<el-skeleton-item variant="text" style="width: 20px" />
										</div>
									</div>
								</div>

								<!-- 操作按钮骨架 -->
								<div class="action-section" style="margin-top: 24px">
									<el-skeleton-item variant="button" style="width: 100px; height: 36px" />
								</div>
							</template>
						</el-skeleton>

						<!-- 实际个人信息内容 -->
						<template v-else>
							<!-- 个人头像和基本信息 -->
							<div class="person-card">
								<div class="person-avatar">
									<div class="avatar-circle">
										{{ getPersonInitials(personInfoFromApi?.personName) }}
									</div>
								</div>
								<div class="person-details">
									<h4 class="person-name">{{ personInfoFromApi?.personName || '员工姓名' }}</h4>
									<div class="person-meta">
										<div class="dept-info">
											<span class="label">所属部门</span>
											<span class="value">{{ personInfoFromApi?.groupName || '员工部门' }}</span>
										</div>
									</div>
								</div>
							</div>

							<!-- 月度统计 -->
							<div class="monthly-stats">
								<h5 class="stats-title">{{ currentMonth }}月排班统计</h5>
								<div class="stats-grid">
									<div class="stat-item">
										<span class="stat-label">总天数</span>
										<span class="stat-value">{{ monthlyStats.totalDays }}</span>
									</div>
									<div class="stat-item">
										<span class="stat-label">工作天数</span>
										<span class="stat-value">{{ monthlyStats.workDays }}</span>
									</div>
									<div class="stat-item">
										<span class="stat-label">已排班</span>
										<span class="stat-value">{{ monthlyStats.scheduledDays }}</span>
									</div>
								</div>
							</div>

							<!-- 操作按钮 -->
							<div class="action-section">
								<button @click="startScheduling" :class="[isSchedulingMode ? 'secondary-btn' : 'primary-btn']">
									{{ isSchedulingMode ? '退出排班' : '开始排班' }}
								</button>
							</div>
						</template>
					</div>

					<!-- 右侧日历区域 -->
					<div class="calendar-section">
						<!-- 骨架屏：日历加载状态 -->
						<el-skeleton v-if="loading" animated>
							<template #template>
								<!-- 日历头部骨架 -->
								<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px">
									<el-skeleton-item variant="button" style="width: 80px; height: 32px" />
									<el-skeleton-item variant="h3" style="width: 150px" />
									<el-skeleton-item variant="button" style="width: 80px; height: 32px" />
								</div>

								<!-- 日历表格骨架 -->
								<div style="border: 1px solid #e4e7ed; border-radius: 4px">
									<!-- 星期标题行 -->
									<div style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e4e7ed">
										<div v-for="i in 7" :key="i" style="padding: 8px; text-align: center; border-right: 1px solid #e4e7ed">
											<el-skeleton-item variant="text" style="width: 20px" />
										</div>
									</div>

									<!-- 日期行骨架 -->
									<div v-for="week in 6" :key="week" style="display: grid; grid-template-columns: repeat(7, 1fr); border-bottom: 1px solid #e4e7ed">
										<div v-for="day in 7" :key="day" style="padding: 8px; height: 80px; border-right: 1px solid #e4e7ed">
											<el-skeleton-item variant="text" style="width: 20px; margin-bottom: 4px" />
											<el-skeleton-item variant="text" style="width: 60px; height: 16px" />
										</div>
									</div>
								</div>
							</template>
						</el-skeleton>

						<!-- 实际日历内容 -->
						<template v-else>
							<!-- Element Plus 日历组件 -->
							<el-calendar v-model="currentDate" :key="calendarKey" ref="calendarRef" class="personal-calendar">
								<!-- 自定义日期单元格内容 -->
								<template #date-cell="{ data }">
									<div
										:class="[
											'calendar-cell-content',
											{
												'is-today': isToday(data.date),
												'is-weekend': isWeekend(data.date),
												'has-schedule': getScheduleForDate(formatDate(data.date)),
												'has-abnormal': getScheduleForDate(formatDate(data.date))?.isAbnormal,
												'has-attendance-result':
													getScheduleForDate(formatDate(data.date))?.attendanceResult &&
													getScheduleForDate(formatDate(data.date))?.attendanceResult !== '未计算',
												'other-month': data.type !== 'current-month',
												'drop-zone': isSchedulingMode && !isMultiSelectMode,
												'is-selected': selectedDates.has(formatDate(data.date)),
												'is-selectable': isSchedulingMode,
												'is-range-selecting': isRangeSelecting && isInRangeSelection(formatDate(data.date)),
												'is-in-selected-range': isInSelectedRange(formatDate(data.date)),
												'is-range-start': selectedRange && formatDate(data.date) === selectedRange.start,
												'is-range-end': selectedRange && formatDate(data.date) === selectedRange.end,
											},
										]"
										@click="handleDayClick(data, $event)"
										@mousedown="handleRangeSelectionStart(data, $event)"
										@mousemove="handleRangeSelectionMove(data, $event)"
										@mouseup="handleRangeSelectionEnd(data, $event)"
										@dragstart.prevent
										@dragover.prevent="handleDragOver"
										@dragenter.prevent="handleDragEnter"
										@dragleave="handleDragLeave"
										@drop="handleDrop(data, $event)"
									>
										<!-- 右上角班次类型角标 -->
										<div
											v-if="getShiftTypeIndicator(formatDate(data.date))"
											class="shift-type-badge"
											:class="`badge-${getShiftTypeIndicator(formatDate(data.date))?.badgeClass}`"
											:title="getShiftTypeText(formatDate(data.date))"
										>
											{{ getShiftTypeIndicator(formatDate(data.date))?.text }}
										</div>

										<!-- 底部状态点：请假/出差/加班/调休/替班/换班（最多3个） -->
										<div v-if="getStatusDots(formatDate(data.date)).length" class="cell__dots">
											<i
												v-for="dot in getStatusDots(formatDate(data.date)).slice(0, 3)"
												:key="dot"
												:class="['dot', `dot--${dot}`]"
												:title="dotLabel(dot)"
											/>
										</div>

										<div class="day-number">{{ data.day.split('-').pop() }}</div>
										<div class="schedule-content">
											<template v-if="getScheduleForDate(formatDate(data.date))">
												<div class="shift-info">
													<!-- 优先显示班组名称 -->
													<div v-if="getScheduleForDate(formatDate(data.date))?.attendanceShiftGroupName" class="shift-group">
														{{ getScheduleForDate(formatDate(data.date)).attendanceShiftGroupName }}
													</div>
													<!-- 显示班次信息 - 只有休班时才显示 ShiftInfo -->
													<div
														v-if="isRestDay(getScheduleForDate(formatDate(data.date))) && getScheduleForDate(formatDate(data.date))?.shiftInfo"
														class="shift-name"
													>
														{{ getScheduleForDate(formatDate(data.date)).shiftInfo }}
													</div>
													<!-- 显示时间信息 -->
													<div v-if="getScheduleForDate(formatDate(data.date))?.startTime" class="shift-time">
														{{ getScheduleForDate(formatDate(data.date)).startTime }}-{{ getScheduleForDate(formatDate(data.date)).endTime }}
													</div>
													<!-- 显示考勤结果标签 (只显示已计算的结果) -->
													<div
														v-if="
															getScheduleForDate(formatDate(data.date))?.attendanceResult &&
															getScheduleForDate(formatDate(data.date))?.attendanceResult !== '未计算'
														"
														class="attendance-result"
													>
														<el-tag
															:type="getAttendanceResultTagType(getScheduleForDate(formatDate(data.date)).attendanceResult)"
															size="small"
															round
															class="attendance-badge"
														>
															{{ getScheduleForDate(formatDate(data.date)).attendanceResult }}
														</el-tag>
													</div>
												</div>
											</template>
											<div v-else class="no-schedule">
												<el-text type="info" size="small" class="empty-text">无排班</el-text>
											</div>
										</div>
									</div>
								</template>
							</el-calendar>
						</template>
					</div>
				</div>
			</div>
		</div>

		<!-- 排班详细信息悬浮卡片 - 使用 Element Plus 组件 -->
		<el-card
			v-if="showScheduleDetail"
			ref="scheduleDetailCardRef"
			class="schedule-detail-card"
			:style="{ left: cardPosition.x + 'px', top: cardPosition.y + 'px', transform: cardTransform }"
			shadow="always"
			@click.stop
		>
			<template #header>
				<div class="card-header-content">
					<div class="date-info">
						<span class="date-text">{{ formatDisplayDate(selectedScheduleDetail?.date) }}</span>
						<span class="weekday-text">{{ formatWeekday(selectedScheduleDetail?.date) }}</span>
					</div>
				</div>
			</template>

			<el-descriptions :column="1" size="small" border v-if="selectedScheduleDetail?.schedule">
				<el-descriptions-item label="员工姓名">
					{{ selectedScheduleDetail?.personInfo?.personName || personInfoFromApi?.personName }}
				</el-descriptions-item>
				<el-descriptions-item label="员工编号">
					{{ selectedScheduleDetail?.personInfo?.personCode || personInfoFromApi?.personCode }}
				</el-descriptions-item>
				<el-descriptions-item label="班次">
					<el-tag :type="getScheduleTagType(selectedScheduleDetail.schedule.shiftName)" size="small" round>
						{{ selectedScheduleDetail.schedule.shiftName }}
					</el-tag>
				</el-descriptions-item>
				<el-descriptions-item label="工作时间" v-if="selectedScheduleDetail.schedule.WorkStartTime && selectedScheduleDetail.schedule.WorkEndTime">
					<el-text size="small" type="primary">
						{{ selectedScheduleDetail.schedule.WorkStartTime }} - {{ selectedScheduleDetail.schedule.WorkEndTime }}
					</el-text>
				</el-descriptions-item>
				<el-descriptions-item label="实际打卡时间">
					<div class="punch-time-info">
						<!-- 上班打卡时间 -->
						<div v-if="selectedScheduleDetail.schedule.StartTime" class="punch-time-item">
							<el-text size="small" type="success">
								<el-icon><Clock /></el-icon>
								上班打卡：{{ selectedScheduleDetail.schedule.StartTime }}
							</el-text>
						</div>
						<!-- 下班打卡时间 -->
						<div v-if="selectedScheduleDetail.schedule.EndTime" class="punch-time-item">
							<el-text size="small" type="primary">
								<el-icon><Clock /></el-icon>
								下班打卡：{{ selectedScheduleDetail.schedule.EndTime }}
							</el-text>
						</div>
						<!-- 如果没有打卡记录 -->
						<div v-if="!selectedScheduleDetail.schedule.StartTime && !selectedScheduleDetail.schedule.EndTime" class="punch-time-item">
							<el-text size="small" type="info">
								<el-icon><Clock /></el-icon>
								暂无打卡记录
							</el-text>
						</div>
					</div>
				</el-descriptions-item>
				<!-- 班次状态 - 新增 -->
				<el-descriptions-item label="班次状态" v-if="selectedScheduleDetail.schedule.ShiftType || selectedScheduleDetail.schedule.shiftType">
					<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
						<el-tag
							:type="getScheduleTagType(selectedScheduleDetail.schedule.ShiftType || selectedScheduleDetail.schedule.shiftType)"
							size="small"
							round
						>
							{{ selectedScheduleDetail.schedule.ShiftType || selectedScheduleDetail.schedule.shiftType }}
						</el-tag>
						<el-tag v-if="selectedScheduleDetail.schedule.isAbnormal" type="danger" size="small"> 异常 </el-tag>
						<!-- 取消按钮 - 仅在非正常上班状态时显示 -->
						<el-button
							v-if="(selectedScheduleDetail.schedule.ShiftType || selectedScheduleDetail.schedule.shiftType) !== '正常上班'"
							type="danger"
							size="small"
							@click="cancelScheduleStatus"
							:loading="cancelLoading"
						>
							{{ cancelLoading ? '取消中...' : '取消' }}
						</el-button>
					</div>
				</el-descriptions-item>
				<!-- 异常考勤更换为正常排班按钮 -->
				<el-descriptions-item
					v-if="
						selectedScheduleDetail.schedule.isAbnormal &&
						(selectedScheduleDetail.schedule.attendanceResult || selectedScheduleDetail.schedule.AttendanceResult) &&
						((selectedScheduleDetail.schedule.attendanceResult &&
							selectedScheduleDetail.schedule.attendanceResult !== '正常' &&
							selectedScheduleDetail.schedule.attendanceResult !== '未计算') ||
							(selectedScheduleDetail.schedule.AttendanceResult &&
								selectedScheduleDetail.schedule.AttendanceResult !== '正常' &&
								selectedScheduleDetail.schedule.AttendanceResult !== '未计算'))
					"
					label="操作"
				>
					<el-button type="primary" size="small" @click="changeToNormalSchedule" :loading="cancelLoading">
						{{ cancelLoading ? '处理中...' : '更换为正常排班' }}
					</el-button>
				</el-descriptions-item>
				<el-descriptions-item label="考勤结果" v-if="selectedScheduleDetail.schedule.AttendanceResult">
					<el-tag :type="getAttendanceResultTagType(selectedScheduleDetail.schedule.AttendanceResult)" size="small" round>
						{{ selectedScheduleDetail.schedule.AttendanceResult }}
					</el-tag>
				</el-descriptions-item>
				<!-- 替班人员 - 新增 -->
				<el-descriptions-item
					label="替班人员"
					v-if="selectedScheduleDetail.schedule.SubstitutePersonName || selectedScheduleDetail.schedule.substitutePersonName"
				>
					<el-text size="small" type="warning">
						{{ selectedScheduleDetail.schedule.SubstitutePersonName || selectedScheduleDetail.schedule.substitutePersonName }}
					</el-text>
				</el-descriptions-item>
			</el-descriptions>

			<el-empty v-else :description="selectedScheduleDetail?.isWeekend ? '周末休息' : '暂无排班安排'" :image-size="60">
				<el-text type="info" size="small">{{ selectedScheduleDetail?.isWeekend ? '今日为休息日' : '该日期暂未安排工作' }}</el-text>
			</el-empty>
		</el-card>

		<!-- 点击遮罩关闭卡片 -->
		<div v-if="showScheduleDetail" class="card-overlay" @click="closeScheduleDetail"></div>

		<!-- 范围选择操作面板 -->
		<el-card
			v-if="showRangePanel"
			class="range-selection-panel"
			:style="{ left: rangePanelPosition.x + 'px', top: rangePanelPosition.y + 'px' }"
			shadow="always"
			@click.stop
		>
			<template #header>
				<div class="panel-header">
					<div class="range-info">
						<span class="range-text">{{ getRangeDisplayText() }}</span>
					</div>
					<button @click="closeRangePanel" class="panel-close-btn">✕</button>
				</div>
			</template>

			<div class="panel-body">
				<el-form :model="rangeForm" label-width="100px" size="default" class="range-form">
					<!-- 班次类型 -->
					<el-form-item label="班次类型" required>
						<el-radio-group v-model="rangeForm.ShiftType" @change="handleShiftTypeChange">
							<el-radio
								v-for="type in availableShiftTypes"
								:key="type.value"
								:value="type.value"
								:disabled="hasNoScheduleInRange && type.value !== '排班'"
							>
								{{ type.label }}
							</el-radio>
						</el-radio-group>
						<div v-if="hasNoScheduleInRange" class="form-tip">
							<el-text type="warning" size="small">
								<el-icon><Warning /></el-icon>
								选中范围包含无排班状态，只能设置为排班
							</el-text>
						</div>
						<div v-if="hasAttendanceResultInRange" class="form-tip">
							<el-text type="danger" size="small">
								<el-icon><Warning /></el-icon>
								选中范围包含已计算的考勤结果，不允许修改排班
							</el-text>
						</div>
						<div v-if="hasAbnormalInRange" class="form-tip">
							<el-text type="info" size="small">
								<el-icon><Warning /></el-icon>
								选中范围包含异常考勤，只能更换为正常上班
							</el-text>
						</div>
					</el-form-item>

					<!-- 换班相关配置 (当选择换班时显示) -->
					<template v-if="rangeForm.ShiftType === '换班'">
						<!-- 选择换班人员 -->
						<el-form-item label="换班人员" required>
							<PersonSelector
								v-model="rangeForm.SwapPersonId"
								placeholder="请输入工号/姓名查询本部门人员（不能选择自己）"
								:clearable="true"
								:group-id="personInfoFromApi?.groupId || personInfoFromApi?.GroupId"
								:exclude-person-ids="props.personInfo?.personId ? [props.personInfo.personId] : []"
								@change="handleSwapPersonChange"
							/>
						</el-form-item>

						<!-- 选择换班记录 -->
						<el-form-item v-if="rangeForm.SwapPersonId" label="换班记录" required>
							<el-select
								v-model="rangeForm.SwapShiftId"
								placeholder="请选择换班记录"
								style="width: 100%"
								:loading="swapShiftLoading"
								:disabled="swapShiftOptions.length === 0 && !swapShiftLoading"
							>
								<el-option v-for="option in swapShiftOptions" :key="option.value" :label="option.label" :value="option.value" />
								<el-option v-if="swapShiftOptions.length === 0 && !swapShiftLoading" value="" label="该人员暂无可用的排班记录" disabled />
							</el-select>
						</el-form-item>
					</template>

					<!-- 替班人员 (当选择替班时显示) -->
					<el-form-item v-if="rangeForm.ShiftType === '替班'" label="替班人员" required>
						<PersonSelector
							v-model="rangeForm.SubstitutePersonId"
							placeholder="请输入工号/姓名查询本部门人员（不能选择自己）"
							:clearable="true"
							:group-id="personInfoFromApi?.groupId || personInfoFromApi?.GroupId"
							:exclude-person-ids="props.personInfo?.personId ? [props.personInfo.personId] : []"
						/>
					</el-form-item>

					<!-- 考勤组选择 (仅排班时显示) -->
					<el-form-item v-if="rangeForm.ShiftType === '排班'" label="考勤组" required>
						<el-select v-model="selectedRangeShift" placeholder="请选择考勤组" style="width: 100%" clearable>
							<el-option v-for="shift in attendanceGroups" :key="shift.ID" :label="`${shift.Name}（${shift.Type}）`" :value="shift.ID">
								<div class="shift-option">
									<span class="shift-name">{{ shift.Name }}</span>
									<span class="shift-type">（{{ shift.Type }}）</span>
								</div>
							</el-option>
						</el-select>
					</el-form-item>

					<!-- 备注 (非排班类型必填，包括正常上班) -->
					<el-form-item v-if="rangeForm.ShiftType !== '排班'" label="备注" required>
						<el-input
							v-model="rangeForm.Remark"
							type="textarea"
							:rows="3"
							:placeholder="rangeForm.ShiftType === '正常上班' ? '请填写更换为正常排班的原因（必填）' : '请填写备注原因（必填）'"
							maxlength="500"
							show-word-limit
							:clearable="true"
						/>
					</el-form-item>

					<!-- 自动计算未来日期 (仅排班时显示) -->
					<el-form-item v-if="rangeForm.ShiftType === '排班'" label="自动计算排班">
						<el-switch
							v-model="rangeForm.AutoCalculateFuture"
							size="default"
							active-text="开启"
							inactive-text="关闭"
							:active-value="true"
							:inactive-value="false"
						/>
						<div class="switch-description">
							<el-text type="info" size="small"> 开启后，排班时会自动计算影响后续日期的排班安排 </el-text>
						</div>
					</el-form-item>
				</el-form>

				<!-- 操作按钮 -->
				<div class="action-buttons">
					<el-button
						@click="saveRangeSchedule"
						type="primary"
						size="small"
						:disabled="(rangeForm.ShiftType === '排班' && !selectedRangeShift) || (hasAttendanceResultInRange && rangeForm.ShiftType !== '正常上班')"
					>
						{{ rangeForm.ShiftType === '正常上班' ? '更换为正常排班' : '保存' }}
					</el-button>
					<el-button @click="closeRangePanel" size="small">取消</el-button>
				</div>
			</div>
		</el-card>

		<!-- 范围面板遮罩 -->
		<div v-if="showRangePanel" class="panel-overlay" @click="closeRangePanel"></div>
	</div>
</template>

<script setup lang="ts">
/**
 * 个人排班日历组件
 * 创建时间: 2025-09-11
 * 最后修改时间: 2025-09-11
 * 修改历史:
 * - 2025-09-11: 初始版本，实现个人排班日历展示功能
 */

import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue';
import { Clock } from '@element-plus/icons-vue';
import {
	ElMessage,
	ElCalendar,
	ElCard,
	ElSelect,
	ElOption,
	ElButton,
	ElTag,
	ElText,
	ElEmpty,
	ElDescriptions,
	ElDescriptionsItem,
	ElIcon,
	ElSwitch,
} from 'element-plus';
import { Check, Delete, CopyDocument, Warning } from '@element-plus/icons-vue';
import { SysAttendanceShiftGroupApi as attendanceGroupApi } from '/@/views/hr/sysattendanceshiftgroup/api';
import { SysAttendanceRecordApi as attendanceApi } from '/@/views/hr/sysattendancerecord/api';
import { ISearchByPersonRequest, IBatchChangeRequest } from '/@/views/hr/sysattendancerecord/api/interface';
import PersonSelector from '/@/components/personSelector/index.vue';
import { ElRadioGroup, ElRadio, ElForm, ElFormItem, ElInput } from 'element-plus';

// 定义组件属性接口
interface PersonBasicInfo {
	personId: string;
	personName: string;
	personCode?: string;
	department?: string;
}

interface PersonScheduleRecord {
	AttendanceShiftGroupId?: string;
	AttendanceShiftGroupName?: string;
	PersonName?: string;
	ShiftDate?: string;
	ShiftName?: string;
	ShiftInfo?: string;
	ShiftType?: string;
	WorkStartTime?: string;
	WorkEndTime?: string;
	StartTime: string;
	EndTime: string;
	Status?: string;
	AttendanceResult?: string;
	ScheduleType?: string;
	IsAbnormal?: boolean;
	DisplayColor?: string;
	SubstitutePersonName?: string;
}

// 定义日历日期接口
interface CalendarDay {
	date: string;
	dayOfMonth: number;
	isCurrentMonth: boolean;
	isToday: boolean;
	isWeekend: boolean;
	schedule?: {
		shiftId: string;
		shiftName: string;
		startTime: string;
		endTime: string;
		status: string;
		scheduleType: string;
		isAbnormal: boolean;
	};
}

// 组件属性定义
interface Props {
	visible: boolean;
	personInfo?: PersonBasicInfo;
}

const props = withDefaults(defineProps<Props>(), {
	visible: false,
	personInfo: undefined,
});

// 组件事件定义
const emit = defineEmits<{
	'update:visible': [visible: boolean];
	close: [];
	'schedule-updated': [data: { type: 'single' | 'batch'; success: boolean }];
}>();

// 响应式数据
const currentDate = ref(new Date());
const calendarRef = ref(null); // 日历组件引用
const calendarKey = ref(Date.now()); // 用于强制重新渲染日历组件
const scheduleRecords = ref<PersonScheduleRecord[]>([]); // 存储从API获取的排班记录
const scheduleRecordsCache = ref<Map<string, PersonScheduleRecord[]>>(new Map()); // 按月份缓存排班记录
const personInfoFromApi = ref<any>(null); // 存储从接口返回的人员信息
const loading = ref(false); // 加载状态

// 数据缓存状态
const lastLoadedPersonId = ref<string | null>(null); // 记录上次加载数据的人员ID
// 排班模式状态
const isSchedulingMode = ref(false);
const showScheduleDetail = ref(false);
const selectedScheduleDetail = ref<any>(null);
const draggedShift = ref<any>(null);

// 自动计算未来排班设置
const autoCalculateFuture = ref(false);

// 取消排班状态加载
const cancelLoading = ref(false);

// 日期选择状态
const selectedDates = ref<Set<string>>(new Set());
const isMultiSelectMode = ref(false);
const selectedShiftForBatch = ref<any>(null);

// 范围选择状态
const isRangeSelecting = ref(false);
const rangeStartDate = ref<string | null>(null);
const rangeEndDate = ref<string | null>(null);
const selectedRange = ref<{ start: string; end: string } | null>(null);
const showRangePanel = ref(false);
const rangePanelPosition = ref({ x: 0, y: 0 });
const selectedRangeShift = ref<string | null>(null);

// 批量排班表单数据
const rangeForm = ref({
	ShiftType: '排班', // 班次类型
	SwapShiftId: '', // 换班记录ID
	SwapPersonId: '', // 换班人员ID
	SubstitutePersonId: '', // 替班人员ID
	AutoCalculateFuture: false, // 自动计算未来日期
	Remark: '', // 备注
});

// 换班相关数据
const swapShiftOptions = ref<Array<{ value: string; label: string }>>([]);
const swapShiftLoading = ref(false);

// 检查选中范围是否包含无排班状态
const hasNoScheduleInRange = computed(() => {
	if (!selectedRange.value) return false;
	const dates = getRangedDates();
	return dates.some((dateStr) => !getScheduleForDate(dateStr));
});

// 检查选中范围是否包含考勤结果（已计算的考勤结果不允许排班）
const hasAttendanceResultInRange = computed(() => {
	if (!selectedRange.value) return false;
	const dates = getRangedDates();
	return dates.some((dateStr) => {
		const schedule = getScheduleForDate(dateStr);
		if (!schedule) return false;
		const result = schedule.attendanceResult || schedule.AttendanceResult || '';
		// 如果有考勤结果且不是"未计算"，则不允许排班
		return result && result !== '未计算';
	});
});

// 检查选中范围是否包含异常考勤
const hasAbnormalInRange = computed(() => {
	if (!selectedRange.value) return false;
	const dates = getRangedDates();
	let totalCount = 0;
	let abnormalCount = 0;

	dates.forEach((dateStr) => {
		const schedule = getScheduleForDate(dateStr);
		if (schedule) {
			totalCount++;
			// 检查是否异常：有异常标识或考勤结果异常
			const isAbnormal = schedule.isAbnormal || false;
			const result = schedule.attendanceResult || schedule.AttendanceResult || '';
			const isAttendanceAbnormal = result && result !== '正常' && result !== '未计算';
			if (isAbnormal || isAttendanceAbnormal) {
				abnormalCount++;
			}
		}
	});

	// 只有当全部选中的日期都是异常时才返回true
	return totalCount > 0 && abnormalCount === totalCount;
});

// 可选择的班次类型
const availableShiftTypes = computed(() => {
	const allTypes = [
		{ label: '排班', value: '排班' },
		{ label: '请假', value: '请假' },
		{ label: '调休', value: '调休' },
		{ label: '替班', value: '替班' },
		{ label: '换班', value: '换班' },
	];

	// 如果选中的单元格包含"无排班"状态，只能选择"排班"
	if (hasNoScheduleInRange.value) {
		return [{ label: '排班', value: '排班' }];
	}

	// 如果选中的单元格全部为考勤异常，只显示"正常上班"选项
	if (hasAbnormalInRange.value) {
		return [{ label: '正常上班', value: '正常上班' }];
	}

	return allTypes;
});

// 监听无排班状态变化，自动重置班次类型
watch(hasNoScheduleInRange, (hasNoSchedule) => {
	if (hasNoSchedule && rangeForm.value.ShiftType !== '排班') {
		rangeForm.value.ShiftType = '排班';
		ElMessage.warning('检测到选中范围包含无排班状态，已自动切换为排班');
	}
});

// 监听异常状态变化，自动切换到正常上班
watch(hasAbnormalInRange, (hasAbnormal) => {
	if (hasAbnormal && rangeForm.value.ShiftType !== '正常上班') {
		rangeForm.value.ShiftType = '正常上班';
		ElMessage.warning('检测到选中范围包含异常状态，已自动切换为正常上班');
	}
});

// 监听班次类型变化，清除不相关字段
watch(
	() => rangeForm.value.ShiftType,
	(newType) => {
		// 如果不是正常上班，清除Remark字段
		if (newType !== '正常上班') {
			rangeForm.value.Remark = '';
		}
		// 清除不相关字段
		if (newType !== '换班') {
			rangeForm.value.SwapShiftId = '';
			rangeForm.value.SwapPersonId = '';
		}
		if (newType !== '替班') {
			rangeForm.value.SubstitutePersonId = '';
		}
	}
);

// 计算属性：获取选中的班组对象
const selectedShiftObject = computed(() => {
	if (!selectedRangeShift.value) return null;
	return attendanceGroups.value.find((shift) => shift.ID === selectedRangeShift.value);
});

// 卡片定位
const cardPosition = ref({ x: 0, y: 0 });
const cardTransform = ref('translate(-50%, 0)');
const scheduleDetailCardRef = ref<any>(null);

// 考勤组数据（从班组API获取）
const attendanceGroups = ref<Array<{ ID: string; Name: string; Type: string }>>([]);

// 计算属性
const currentYear = computed(() => currentDate.value.getFullYear());
const currentMonth = computed(() => currentDate.value.getMonth() + 1);

// 月度统计计算
const monthlyStats = computed(() => {
	const year = currentYear.value;
	const month = currentMonth.value;
	const daysInMonth = new Date(year, month, 0).getDate();

	let workDays = 0;
	let scheduledDays = 0;

	if (scheduleRecords.value) {
		// 计算当月的工作日和已排班天数
		for (let day = 1; day <= daysInMonth; day++) {
			const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
			const dayOfWeek = new Date(year, month - 1, day).getDay();

			// 非周末为工作日
			if (dayOfWeek !== 0 && dayOfWeek !== 6) {
				workDays++;
			}

			// 检查是否有排班记录
			const hasSchedule = scheduleRecords.value.some((record) => {
				if (record.ShiftDate) {
					const recordDate = new Date(record.ShiftDate);
					return recordDate.getFullYear() === year && recordDate.getMonth() + 1 === month && recordDate.getDate() === day;
				}
				return false;
			});

			if (hasSchedule) {
				scheduledDays++;
			}
		}
	}

	return {
		totalDays: daysInMonth,
		workDays,
		scheduledDays,
	};
});

// Element Plus 日历相关方法

/**
 * 格式化日期为 YYYY-MM-DD 格式（使用本地时间）
 * @param date 日期对象
 * @returns 格式化的日期字符串
 */
const formatDate = (date: Date): string => {
	// 使用本地时间而不是UTC时间，确保用户选择的日期与传递给后端的日期一致
	const year = date.getFullYear();
	const month = (date.getMonth() + 1).toString().padStart(2, '0');
	const day = date.getDate().toString().padStart(2, '0');
	return `${year}-${month}-${day}`;
};

/**
 * 判断是否为今天
 * @param date 日期对象
 * @returns 是否为今天
 */
const isToday = (date: Date): boolean => {
	const today = new Date();
	return today.getFullYear() === date.getFullYear() && today.getMonth() === date.getMonth() && today.getDate() === date.getDate();
};

/**
 * 判断是否为周末
 * @param date 日期对象
 * @returns 是否为周末
 */
const isWeekend = (date: Date): boolean => {
	const dayOfWeek = date.getDay();
	return dayOfWeek === 0 || dayOfWeek === 6;
};

// 方法定义

/**
 * 判断是否为休班
 * @param schedule 排班信息
 * @returns 是否为休班
 */
const isRestDay = (schedule: any): boolean => {
	if (!schedule) return false;

	// 检查班次信息是否包含"休"字
	const shiftInfo = schedule.shiftInfo || schedule.ShiftInfo || '';
	const shiftName = schedule.shiftName || '';
	const shiftType = schedule.shiftType || schedule.ShiftType || '';

	// 判断休班的条件
	return (
		shiftInfo === '休' ||
		shiftInfo.includes('休班') ||
		shiftName === '休' ||
		shiftName.includes('休班') ||
		shiftType === '休' ||
		shiftType.includes('休班')
	);
};

/**
 * 获取指定日期的排班信息
 * @param dateStr 日期字符串 (YYYY-MM-DD)
 * @returns 排班信息对象或null
 */
const getScheduleForDate = (dateStr: string) => {
	if (!scheduleRecords.value || !Array.isArray(scheduleRecords.value) || scheduleRecords.value.length === 0) {
		return null;
	}

	const targetDate = new Date(dateStr);
	const record = scheduleRecords.value.find((r) => {
		// 使用 ShiftDate 字段
		const recordDate = r.ShiftDate;

		if (!recordDate) return false;

		let recordDateObj: Date;
		if (typeof recordDate === 'string') {
			// 处理字符串格式的日期，去掉时间部分
			const dateStr = recordDate.split('T')[0];
			recordDateObj = new Date(dateStr);
		} else if (recordDate && typeof recordDate === 'object' && 'getTime' in recordDate) {
			recordDateObj = recordDate as Date;
		} else {
			return false;
		}

		return (
			recordDateObj.getFullYear() === targetDate.getFullYear() &&
			recordDateObj.getMonth() === targetDate.getMonth() &&
			recordDateObj.getDate() === targetDate.getDate()
		);
	});

	if (record) {
		return {
			shiftId: record.AttendanceShiftGroupId || '',
			// 优先使用班组名称，不再使用 ShiftInfo
			shiftName: record.AttendanceShiftGroupName || record.ShiftName || `班次${record.AttendanceShiftGroupId || ''}`,
			startTime: record.WorkStartTime || '',
			endTime: record.WorkEndTime || '',
			status: record.Status || '',
			scheduleType: record.ScheduleType || '',
			attendanceResult: record.AttendanceResult || '',
			isAbnormal: record.IsAbnormal || false,
			// 添加额外的展示信息 - ShiftInfo 保留用于休班显示
			shiftInfo: record.ShiftInfo || '',
			attendanceShiftGroupName: record.AttendanceShiftGroupName || '',
			// 直接使用接口定义的字段名
			WorkStartTime: record.WorkStartTime || '',
			WorkEndTime: record.WorkEndTime || '',
			StartTime: record.StartTime || '',
			EndTime: record.EndTime || '',
			AttendanceResult: record.AttendanceResult || '',
			// 班次类型相关字段
			ShiftType: record.ShiftType || '',
			shiftType: record.ShiftType || '',
			// ShiftInfo 保留用于休班显示
			ShiftInfo: record.ShiftInfo || '',
			// 替班人员信息
			SubstitutePersonName: record.SubstitutePersonName || '',
			substitutePersonName: record.SubstitutePersonName || '',
		};
	}

	// 如果Records不为空但没有找到该日期的记录，返回null显示"无排班"
	return null;
};

/**
 * 获取人员姓名首字母
 * @param name 姓名
 * @returns 首字母或最后两个字符
 */
const getPersonInitials = (name?: string): string => {
	if (!name) return '';
	const chars = name.split('');
	if (chars.length === 1) return chars[0];
	return chars.slice(-2).join(''); // 取最后两个字符（中文姓名）
};

/**
 * 获取排班类型对应的 Element Plus tag 类型
 * @param shiftName 班次名称或班次类型
 * @returns Element Plus tag type
 */
const getScheduleTagType = (shiftName: string): 'primary' | 'success' | 'warning' | 'danger' | 'info' => {
	const tagTypes: { [key: string]: 'primary' | 'success' | 'warning' | 'danger' | 'info' } = {
		常白班: 'primary',
		早班: 'success',
		中班: 'warning',
		夜班: 'danger',
		休班: 'info',
		请假: 'warning',
		出差: 'primary',
		培训: 'success',
		排班: 'primary',
		调休: 'success',
		替班: 'info',
		换班: 'warning',
		正常上班: 'primary',
	};
	return tagTypes[shiftName] || 'info';
};

/**
 * 获取考勤结果对应的 Element Plus tag 类型
 * @param result 考勤结果
 * @returns Element Plus tag type
 */
const getAttendanceResultTagType = (result: string): 'primary' | 'success' | 'warning' | 'danger' | 'info' => {
	if (!result) return 'info';

	// 根据后端 AttendanceResultEnum 枚举值进行匹配
	switch (result) {
		case '未计算':
			return 'info';
		case '正常':
			return 'success';
		case '迟到':
			return 'warning';
		case '早退':
			return 'warning';
		case '迟到且早退':
			return 'danger';
		case '缺卡':
			return 'danger';
		case '旷工':
			return 'danger';
		default:
			// 兼容其他可能的值，使用原来的模糊匹配逻辑
			const lowerResult = result.toLowerCase();
			if (lowerResult.includes('正常') || lowerResult.includes('出勤')) {
				return 'success';
			} else if (lowerResult.includes('迟到') || lowerResult.includes('早退')) {
				return 'warning';
			} else if (lowerResult.includes('缺勤') || lowerResult.includes('旷工') || lowerResult.includes('缺卡')) {
				return 'danger';
			} else if (lowerResult.includes('请假') || lowerResult.includes('休假')) {
				return 'info';
			} else {
				return 'primary';
			}
	}
};

/**
 * 关闭弹窗
 */
const handleClose = (): void => {
	emit('update:visible', false);
	emit('close');
};

/**
 * 关闭排班详情弹窗
 */
const closeScheduleDetail = (): void => {
	showScheduleDetail.value = false;
	selectedScheduleDetail.value = null;
};

/**
 * 取消排班状态
 */
const cancelScheduleStatus = async (): Promise<void> => {
	if (!selectedScheduleDetail.value) {
		ElMessage.error('未选择排班记录');
		return;
	}

	try {
		cancelLoading.value = true;

		const { date, schedule } = selectedScheduleDetail.value;
		const personId = props.personInfo?.personId || personInfoFromApi.value?.personId;

		if (!personId) {
			ElMessage.error('人员信息不完整');
			return;
		}

		// 检查是否有考勤结果
		const result = schedule.attendanceResult || schedule.AttendanceResult || '';
		if (result && result !== '未计算') {
			ElMessage.error('该日期已有考勤结果，不允许修改排班');
			return;
		}

		// 使用batchChange接口取消特殊状态（如请假、调休等），恢复为正常排班
		await attendanceApi().batchChange({
			PersonIds: [personId],
			DateRange: [date, date],
			AttendanceShiftGroupId: schedule.shiftId || schedule.AttendanceShiftGroupId || '',
			ShiftType: '排班', // 恢复为正常排班状态
			AutoCalculateFuture: false,
		});

		ElMessage.success('排班状态已取消');

		// 刷新排班数据
		await loadPersonalScheduleData(true);

		// 关闭详情弹窗
		closeScheduleDetail();
	} catch (error) {
		console.error('取消排班状态失败:', error);
		ElMessage.error('取消排班状态失败，请重试');
	} finally {
		cancelLoading.value = false;
	}
};

/**
 * 异常考勤更换为正常排班
 */
const changeToNormalSchedule = async (): Promise<void> => {
	if (!selectedScheduleDetail.value) {
		ElMessage.error('未选择排班记录');
		return;
	}

	try {
		cancelLoading.value = true;

		const { date, schedule } = selectedScheduleDetail.value;
		const personId = props.personInfo?.personId || personInfoFromApi.value?.personId;

		if (!personId) {
			ElMessage.error('人员信息不完整');
			return;
		}

		// 使用 ElMessageBox 让用户输入备注
		const { ElMessageBox } = await import('element-plus');
		const { value: remark } = await ElMessageBox.prompt('请填写更换为正常排班的原因', '更换为正常排班', {
			confirmButtonText: '确定',
			cancelButtonText: '取消',
			inputType: 'textarea',
			inputPlaceholder: '请输入备注原因（必填）',
			inputValidator: (value: string) => {
				if (!value || !value.trim()) {
					return '备注原因不能为空';
				}
				return true;
			},
		});

		// 使用batchChange接口将异常考勤更换为正常排班
		await attendanceApi().batchChange({
			PersonIds: [personId],
			DateRange: [date, date],
			AttendanceShiftGroupId: schedule.shiftId || schedule.AttendanceShiftGroupId || '',
			ShiftType: '正常上班', // 更换为正常上班
			AutoCalculateFuture: false,
			Remark: remark.trim(),
		});

		ElMessage.success('已更换为正常排班');

		// 刷新排班数据
		await loadPersonalScheduleData(true);

		// 关闭详情弹窗
		closeScheduleDetail();
	} catch (error: any) {
		// 用户取消输入时不显示错误
		if (error !== 'cancel') {
			console.error('更换为正常排班失败:', error);
			ElMessage.error('更换为正常排班失败，请重试');
		}
	} finally {
		cancelLoading.value = false;
	}
};

/**
 * 格式化显示日期
 */
const formatDisplayDate = (dateStr?: string): string => {
	if (!dateStr) return '';
	const [, month, day] = dateStr.split('-');
	return `${month}/${day}`;
};

/**
 * 格式化星期
 */
const formatWeekday = (dateStr?: string): string => {
	if (!dateStr) return '';
	const date = new Date(dateStr);
	const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
	return weekdays[date.getDay()];
};

/**
 * 获取状态点（用于显示请假、出差、加班、调休、替班、换班等标记）
 */
const getStatusDots = (dateStr: string): string[] => {
	const schedule = getScheduleForDate(dateStr);
	if (!schedule) return [];

	const shiftType = schedule.ShiftType || schedule.shiftType || '';
	const dots: string[] = [];
	if (shiftType.includes('请假')) dots.push('leave');
	if (shiftType.includes('出差')) dots.push('trip');
	if (shiftType.includes('加班')) dots.push('ot');
	if (shiftType.includes('调休')) dots.push('offinlieu');
	if (shiftType.includes('替班')) dots.push('substitute');
	if (shiftType.includes('换班')) dots.push('shift-change');
	return dots;
};

/**
 * 获取状态点标签
 */
const dotLabel = (dot: string): string => {
	switch (dot) {
		case 'leave':
			return '请假';
		case 'trip':
			return '出差';
		case 'ot':
			return '加班';
		case 'offinlieu':
			return '调休';
		case 'substitute':
			return '替班';
		case 'shift-change':
			return '换班';
		default:
			return '';
	}
};

/**
 * 获取班次类型标识（替班、换班等）
 */
const getShiftTypeIndicator = (dateStr: string) => {
	const schedule = getScheduleForDate(dateStr);
	if (!schedule) return null;

	const shiftType = schedule.ShiftType || schedule.shiftType || '';

	// 根据班次类型返回标识信息
	const indicators: Record<string, { text: string; color: string; bgColor: string; badgeClass: string }> = {
		请假: { text: '假', color: '#16a34a', bgColor: '#f0fdf4', badgeClass: 'leave' },
		调休: { text: '调', color: '#059669', bgColor: '#ecfdf5', badgeClass: 'adjustment' },
		替班: { text: '替', color: '#0891b2', bgColor: '#f0f9ff', badgeClass: 'substitute' },
		换班: { text: '换', color: '#7c3aed', bgColor: '#faf5ff', badgeClass: 'exchange' },
		加班: { text: '加', color: '#dc2626', bgColor: '#fef2f2', badgeClass: 'overtime' },
		休班: { text: '休', color: '#ca8a04', bgColor: '#fffbeb', badgeClass: 'rest' },
	};

	// 查找匹配的班次类型
	for (const [key, value] of Object.entries(indicators)) {
		if (shiftType.includes(key)) {
			return value;
		}
	}

	return null;
};

/**
 * 获取班次类型文本
 */
const getShiftTypeText = (dateStr: string): string => {
	const schedule = getScheduleForDate(dateStr);
	if (!schedule) return '';
	return schedule.ShiftType || schedule.shiftType || '';
};

/**
 * 开始范围选择
 * @param data Element Plus 日历的日期数据
 * @param event 鼠标事件
 */
const handleRangeSelectionStart = (data: any, event: MouseEvent): void => {
	// 只有在排班模式下才能进行范围选择
	if (!isSchedulingMode.value) {
		event.preventDefault();
		return;
	}

	const dateStr = formatDate(data.date);
	isRangeSelecting.value = true;
	rangeStartDate.value = dateStr;
	rangeEndDate.value = dateStr;
	selectedRange.value = null;
	showRangePanel.value = false;

	// 阻止默认行为
	event.preventDefault();
};

/**
 * 范围选择过程中
 * @param data Element Plus 日历的日期数据
 * @param event 鼠标事件
 */
const handleRangeSelectionMove = (data: any, event: MouseEvent): void => {
	// 只有在排班模式下才能进行范围选择
	if (!isSchedulingMode.value || !isRangeSelecting.value || !rangeStartDate.value) return;

	const dateStr = formatDate(data.date);
	rangeEndDate.value = dateStr;

	// 阻止默认行为
	event.preventDefault();
};

/**
 * 结束范围选择
 * @param data Element Plus 日历的日期数据
 * @param event 鼠标事件
 */
const handleRangeSelectionEnd = (data: any, event: MouseEvent): void => {
	// 只有在排班模式下才能进行范围选择
	if (!isSchedulingMode.value || !isRangeSelecting.value || !rangeStartDate.value) return;

	const dateStr = formatDate(data.date);
	rangeEndDate.value = dateStr;

	// 确定选择范围
	const startDate = rangeStartDate.value;
	const endDate = dateStr;

	// 确保开始日期小于结束日期
	const start = new Date(startDate);
	const end = new Date(endDate);
	if (start > end) {
		selectedRange.value = { start: endDate, end: startDate };
	} else {
		selectedRange.value = { start: startDate, end: endDate };
	}

	// 显示操作面板
	showRangePanel.value = true;

	// 计算面板位置（考虑屏幕边界，优先居中显示）
	const viewportWidth = window.innerWidth;
	const viewportHeight = window.innerHeight;

	// 根据屏幕尺寸判断设备类型
	const isMobile = viewportWidth <= 768; // 移动端
	const isTablet = viewportWidth > 768 && viewportWidth <= 1024; // 平板
	const isSmallScreen = viewportWidth <= 480; // 小屏幕手机

	// 根据设备类型设置弹窗尺寸
	let panelWidth: number;
	if (isSmallScreen) {
		panelWidth = viewportWidth - 20; // 小屏幕几乎全屏
	} else if (isMobile) {
		panelWidth = Math.min(360, viewportWidth - 40);
	} else if (isTablet) {
		panelWidth = Math.min(420, viewportWidth - 60);
	} else {
		panelWidth = 480; // 桌面端
	}

	const panelHeight = Math.min(600, viewportHeight - 40); // 弹窗最大高度
	const margin = isSmallScreen ? 10 : isMobile ? 20 : 30; // 距离屏幕边缘的最小间距

	// 优先居中显示
	let x = (viewportWidth - panelWidth) / 2;
	let y = (viewportHeight - panelHeight) / 2;

	// 水平边界检测和调整
	if (x < margin) {
		x = margin;
	} else if (x + panelWidth > viewportWidth - margin) {
		x = viewportWidth - panelWidth - margin;
	}

	// 垂直边界检测和调整
	if (y < margin) {
		y = margin;
	} else if (y + panelHeight > viewportHeight - margin) {
		y = viewportHeight - panelHeight - margin;
	}

	rangePanelPosition.value = { x, y };

	// 重置选择状态
	isRangeSelecting.value = false;
	rangeStartDate.value = null;
	rangeEndDate.value = null;

	// 阻止默认行为
	event.preventDefault();
};

/**
 * 判断日期是否在选择范围中（正在选择过程中）
 * @param dateStr 日期字符串
 * @returns 是否在选择范围中
 */
const isInRangeSelection = (dateStr: string): boolean => {
	if (!rangeStartDate.value || !rangeEndDate.value) return false;

	const date = new Date(dateStr);
	const start = new Date(rangeStartDate.value);
	const end = new Date(rangeEndDate.value);

	// 确保开始日期小于结束日期
	const actualStart = start <= end ? start : end;
	const actualEnd = start <= end ? end : start;

	return date >= actualStart && date <= actualEnd;
};

/**
 * 判断日期是否在已选择范围中
 * @param dateStr 日期字符串
 * @returns 是否在已选择范围中
 */
const isInSelectedRange = (dateStr: string): boolean => {
	if (!selectedRange.value) return false;

	const date = new Date(dateStr);
	const start = new Date(selectedRange.value.start);
	const end = new Date(selectedRange.value.end);

	return date >= start && date <= end;
};

/**
 * 获取选择范围的显示文本
 * @returns 范围显示文本
 */
const getRangeDisplayText = (): string => {
	if (!selectedRange.value) return '';

	const startDate = new Date(selectedRange.value.start);
	const endDate = new Date(selectedRange.value.end);

	if (selectedRange.value.start === selectedRange.value.end) {
		return `${formatDisplayDate(selectedRange.value.start)} ${formatWeekday(selectedRange.value.start)}`;
	}

	return `${formatDisplayDate(selectedRange.value.start)} - ${formatDisplayDate(selectedRange.value.end)}`;
};

/**
 * 关闭范围面板
 */
const closeRangePanel = (): void => {
	showRangePanel.value = false;
	selectedRange.value = null;
	selectedRangeShift.value = null;
	// 重置表单
	rangeForm.value = {
		ShiftType: '排班',
		SwapShiftId: '',
		SwapPersonId: '',
		SubstitutePersonId: '',
		AutoCalculateFuture: false,
		Remark: '',
	};
	swapShiftOptions.value = [];
};

/**
 * 处理班次类型变化
 */
const handleShiftTypeChange = (): void => {
	// 清除不相关字段
	if (rangeForm.value.ShiftType !== '换班') {
		rangeForm.value.SwapShiftId = '';
		rangeForm.value.SwapPersonId = '';
	}
	if (rangeForm.value.ShiftType !== '替班') {
		rangeForm.value.SubstitutePersonId = '';
	}
	if (rangeForm.value.ShiftType === '排班') {
		rangeForm.value.Remark = '';
	}
	// 正常上班需要备注，但这里不清除，让用户填写
};

/**
 * 处理换班人员变化
 */
const handleSwapPersonChange = async (personId: string): Promise<void> => {
	if (!personId) {
		swapShiftOptions.value = [];
		rangeForm.value.SwapShiftId = '';
		return;
	}

	swapShiftLoading.value = true;
	try {
		// 查询该人员未来30天的排班记录
		const today = new Date();
		const futureDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
		const params: ISearchByPersonRequest = {
			PersonId: personId,
			ShiftDate: [formatDate(today), formatDate(futureDate)],
		};

		const response = await attendanceApi().getSchedulingByPerson(params);

		if (response.Data && Array.isArray(response.Data) && response.Data.length > 0) {
			const firstData = (response.Data as any[])[0];
			if (firstData && firstData.Records && Array.isArray(firstData.Records)) {
				const filteredData = firstData.Records.filter((record: any) => {
					return record.ID && record.ShiftDate && record.AttendanceShiftGroupName;
				});

				swapShiftOptions.value = filteredData.map((record: any) => {
					// 优先使用完整班组信息，不再使用 ShiftInfo
					const shiftName = record.AttendanceShiftGroupName || record.AttendanceShift?.Name || record.ShiftName || '未知班次';
					// const shiftName = record.AttendanceShift?.Name || record.AttendanceShiftGroupName || record.ShiftName || record.ShiftInfo || '未知班次'; // 注释掉 ShiftInfo
					return {
						value: record.ID,
						label: `${record.ShiftDate} - ${shiftName}`,
					};
				});
			} else {
				swapShiftOptions.value = [];
			}
		} else {
			swapShiftOptions.value = [];
		}

		if (swapShiftOptions.value.length === 0) {
			ElMessage.info('该人员暂无可用的排班记录');
		}
	} catch (error) {
		console.error('查询人员排班记录失败:', error);
		ElMessage.error('查询人员排班记录失败');
		swapShiftOptions.value = [];
	} finally {
		swapShiftLoading.value = false;
	}
};

/**
 * 保存范围排班
 */
const saveRangeSchedule = async (): Promise<void> => {
	if (!selectedRange.value) {
		ElMessage.warning('请选择日期范围');
		return;
	}

	// 检查是否有考勤结果，如果有则不允许排班（但允许正常上班类型，用于更换异常考勤）
	if (hasAttendanceResultInRange.value && rangeForm.value.ShiftType === '排班') {
		ElMessage.error('选中范围包含已计算的考勤结果，不允许修改排班');
		return;
	}

	// 验证必填字段
	if (rangeForm.value.ShiftType === '排班' && !selectedRangeShift.value) {
		ElMessage.error('请选择考勤组');
		return;
	}

	if (rangeForm.value.ShiftType === '正常上班' && !rangeForm.value.Remark?.trim()) {
		ElMessage.error('请填写备注原因');
		return;
	}

	if (rangeForm.value.ShiftType === '替班' && !rangeForm.value.SubstitutePersonId) {
		ElMessage.error('请选择替班人员');
		return;
	}

	if (rangeForm.value.ShiftType === '换班' && (!rangeForm.value.SwapShiftId || !rangeForm.value.SwapPersonId)) {
		ElMessage.error('请完善换班信息');
		return;
	}

	if (rangeForm.value.ShiftType !== '排班' && rangeForm.value.ShiftType !== '正常上班' && !rangeForm.value.Remark?.trim()) {
		ElMessage.error('请填写备注原因');
		return;
	}

	// 获取范围内的所有日期
	const dates = getRangedDates();

	// 构建请求参数
	const request: any = {
		PersonIds: [props.personInfo?.personId],
		ShiftType: rangeForm.value.ShiftType,
		DateRange: [selectedRange.value.start, selectedRange.value.end],
		AutoCalculateFuture: rangeForm.value.AutoCalculateFuture,
	};

	// 设置考勤组ID
	if (rangeForm.value.ShiftType === '排班') {
		request.AttendanceShiftGroupId = selectedRangeShift.value;
	} else if (rangeForm.value.ShiftType === '正常上班') {
		// 正常上班需要从当前排班记录中获取考勤组ID
		const firstDate = dates[0];
		const schedule = getScheduleForDate(firstDate);
		if (schedule && schedule.shiftId) {
			request.AttendanceShiftGroupId = schedule.shiftId;
		}
	} else {
		// 其他班次类型从当前排班记录中获取考勤组ID
		const firstDate = dates[0];
		const schedule = getScheduleForDate(firstDate);
		if (schedule && schedule.shiftId) {
			request.AttendanceShiftGroupId = schedule.shiftId;
		}
	}

	// 根据班次类型添加相应的额外参数
	switch (rangeForm.value.ShiftType) {
		case '替班':
			request.SubstitutePersonId = rangeForm.value.SubstitutePersonId;
			break;
		case '换班':
			request.SwapShiftId = rangeForm.value.SwapShiftId;
			request.SwapPersonId = rangeForm.value.SwapPersonId;
			break;
	}

	// 非排班类型需要传递备注（正常上班也需要备注）
	if (rangeForm.value.ShiftType !== '排班' && rangeForm.value.Remark?.trim()) {
		request.Remark = rangeForm.value.Remark;
	}

	try {
		await attendanceApi().batchChange(request);

		ElMessage.success(`已设置 ${dates.length} 个日期的${rangeForm.value.ShiftType}`);

		// 重新加载当前月份的数据
		await loadPersonalScheduleData(true);

		// 通知父组件数据已更新
		emit('schedule-updated', { type: 'batch', success: true });

		closeRangePanel();
	} catch (error) {
		ElMessage.error('排班设置失败，请检查该时间段是否已有考勤结果');
	}
};

/**
 * 删除范围排班
 */
const deleteRangeSchedule = async (): Promise<void> => {
	if (!selectedRange.value) return;

	// 检查是否有考勤结果
	if (hasAttendanceResultInRange.value) {
		ElMessage.error('选中范围包含已计算的考勤结果，不允许删除排班');
		return;
	}

	try {
		const dates = getRangedDates();

		const request: any = {
			PersonIds: [props.personInfo?.personId],
			ShiftType: '无排班',
			DateRange: [selectedRange.value.start, selectedRange.value.end],
			AutoCalculateFuture: false,
			AttendanceShiftGroupId: null,
		};

		await attendanceApi().batchChange(request);

		ElMessage.success(`已删除 ${dates.length} 个日期的排班`);

		// 重新加载当前月份的数据
		await loadPersonalScheduleData(true);

		// 通知父组件数据已更新
		emit('schedule-updated', { type: 'batch', success: true });

		closeRangePanel();
	} catch (error) {
		ElMessage.error('删除排班失败，请重试');
	}
};

/**
 * 获取范围内的所有日期
 * @returns 日期数组
 */
const getRangedDates = (): string[] => {
	if (!selectedRange.value) return [];

	const dates: string[] = [];
	const start = new Date(selectedRange.value.start);
	const end = new Date(selectedRange.value.end);

	const current = new Date(start);
	while (current <= end) {
		dates.push(formatDate(current));
		current.setDate(current.getDate() + 1);
	}

	return dates;
};

/**
 * 点击日期事件处理（适配 Element Plus 日历组件）
 * @param data Element Plus 日历的日期数据
 * @param event 点击事件
 */
const handleDayClick = (data: any, event?: Event): void => {
	// 如果正在范围选择，则不处理点击事件
	if (isRangeSelecting.value) {
		return;
	}

	const dateStr = formatDate(data.date);
	const schedule = getScheduleForDate(dateStr);

	// 转换为原有的 CalendarDay 格式以保持兼容性
	const calendarDay: CalendarDay = {
		date: dateStr,
		dayOfMonth: data.date.getDate(),
		isCurrentMonth: data.type === 'current-month',
		isToday: isToday(data.date),
		isWeekend: isWeekend(data.date),
		schedule: schedule || undefined,
	};

	if (isSchedulingMode.value) {
		// 排班模式：处理日期选择和班次分配
		const mouseEvent = event as MouseEvent;

		if (isMultiSelectMode.value) {
			// 多选模式：切换日期选择状态
			handleDateSelection(dateStr, mouseEvent?.ctrlKey || mouseEvent?.metaKey || false);
		} else {
			// 单选模式：直接分配班次
			if (selectedShiftForBatch.value) {
				assignShiftToDate(dateStr, selectedShiftForBatch.value);
			} else {
				// Shift selection warning removed
			}
		}
	} else {
		// 查看模式：显示详细信息
		showScheduleDetail.value = true;
		selectedScheduleDetail.value = {
			...calendarDay,
			personInfo: personInfoFromApi.value,
		};

		// 记录点击位置用于卡片定位
		const clickEvent = event?.target as HTMLElement;
		if (clickEvent) {
			const rect = clickEvent.getBoundingClientRect();
			cardPosition.value = {
				x: rect.left + rect.width / 2,
				y: rect.bottom + 8,
			};
			cardTransform.value = 'translate(-50%, 0)';
		}

		// 下一帧计算防溢出位置
		nextTick(() => {
			const cardEl = scheduleDetailCardRef.value?.$el || scheduleDetailCardRef.value;
			if (!cardEl) return;
			const cardRect = cardEl.getBoundingClientRect();
			const vw = window.innerWidth;
			const vh = window.innerHeight;
			const margin = 12;

			let x = cardPosition.value.x;
			let y = cardPosition.value.y;

			// 水平居中并限制左右边界
			const halfWidth = cardRect.width / 2;
			x = Math.max(margin + halfWidth, Math.min(vw - margin - halfWidth, x));

			// 垂直方向：优先显示在目标下方，不够则翻转到上方
			const spaceBelow = vh - cardPosition.value.y - margin;
			const spaceAbove = (event && (event as MouseEvent).clientY ? (event as MouseEvent).clientY : cardPosition.value.y) - margin;
			if (spaceBelow < cardRect.height && spaceAbove > cardRect.height) {
				// 放到上方
				y = (clickEvent ? clickEvent.getBoundingClientRect().top : cardPosition.value.y) - 8;
				cardTransform.value = 'translate(-50%, -100%)';
			} else {
				// 保持在下方，但如果超出底部，则调整到可见区域
				if (y + cardRect.height > vh - margin) {
					y = vh - cardRect.height - margin;
				}
				cardTransform.value = 'translate(-50%, 0)';
			}

			// 应用修正
			cardPosition.value = { x, y };
		});
	}

	// 个人日历组件现在独立处理日期点击，不需要通知父组件
};

/**
 * 处理日期选择（多选模式）
 * @param dateStr 日期字符串
 * @param isCtrlClick 是否按住Ctrl键点击
 */
const handleDateSelection = (dateStr: string, isCtrlClick: boolean): void => {
	if (selectedDates.value.has(dateStr)) {
		// 如果已选中，则取消选择
		selectedDates.value.delete(dateStr);
	} else {
		// 如果未选中，则添加选择
		if (!isCtrlClick && !isMultiSelectMode.value) {
			// 单选模式或未按住Ctrl键，清空之前的选择
			selectedDates.value.clear();
		}
		selectedDates.value.add(dateStr);
	}
};

/**
 * 为单个日期分配班次
 * @param dateStr 日期字符串
 * @param shift 班次信息
 */
const assignShiftToDate = async (dateStr: string, shift: any): Promise<void> => {
	// 直接调用保存方法
	await saveSchedule(dateStr, shift);
};

/**
 * 开始/退出排班
 */
const startScheduling = (): void => {
	isSchedulingMode.value = !isSchedulingMode.value;

	// 退出排班模式时重置状态
	if (!isSchedulingMode.value) {
		selectedDates.value.clear();
		selectedShiftForBatch.value = null;
		isMultiSelectMode.value = false;
	}

	// 个人日历组件现在独立运行，不需要通知父组件

	// Scheduling mode toggle feedback removed
};

// 监听弹窗显示状态
watch(
	() => props.visible,
	(newVisible) => {
		if (newVisible) {
			// 弹窗打开时强制重置到当前月份
			const now = new Date();

			isSettingDateProgrammatically.value = true;

			// 强制重新渲染日历组件
			calendarKey.value = Date.now();
			currentDate.value = new Date(now); // 创建新的Date对象确保更新

			// 下一个tick后恢复
			nextTick(() => {
				isSettingDateProgrammatically.value = false;
			});
		}
	}
);

/**
 * 拖拽进入
 */
const handleDragEnter = (event: DragEvent): void => {
	event.preventDefault();
	if (isSchedulingMode.value) {
		const target = event.currentTarget as HTMLElement;
		target.classList.add('drag-over');
	}
};

/**
 * 拖拽悬停
 */
const handleDragOver = (event: DragEvent): void => {
	event.preventDefault();
};

/**
 * 拖拽离开
 */
const handleDragLeave = (event: DragEvent): void => {
	const target = event.currentTarget as HTMLElement;
	target.classList.remove('drag-over');
};

/**
 * 拖拽放下
 */
const handleDrop = async (data: any, event: DragEvent): Promise<void> => {
	event.preventDefault();
	const target = event.currentTarget as HTMLElement;
	target.classList.remove('drag-over');

	if (!isSchedulingMode.value || !draggedShift.value) {
		return;
	}

	const dateStr = formatDate(data.date);
	const shiftToUpdate = draggedShift.value;

	// 直接调用保存方法
	await saveSchedule(dateStr, shiftToUpdate);

	// 清除拖拽状态
	draggedShift.value = null;
};

/**
 * 保存单个日期的排班
 * @param date 日期字符串 (yyyy-MM-dd)
 * @param shift 班组信息，null表示删除排班
 */
const saveSchedule = async (date: string, shift: any) => {
	if (!props.personInfo?.personId) {
		ElMessage.error('人员信息不完整，无法保存排班');
		return false;
	}

	try {
		const api = attendanceApi();

		// 构建请求参数（使用新的参数结构与团队日历保持一致）
		const request: any = {
			PersonIds: [props.personInfo.personId],
			ShiftType: shift ? '正常上班' : '无排班',
			DateRange: [date, date], // 单个日期也使用日期范围格式
			AutoCalculateFuture: autoCalculateFuture.value, // 使用开关控制是否自动计算未来
			AttendanceShiftGroupId: shift ? shift.ID : null,
		};

		// 调用API保存排班
		await api.batchChange(request);

		// 显示成功消息
		if (shift) {
			ElMessage.success(` 在 ${date} 安排 ${shift.Name}`);
		} else {
			ElMessage.success(`在 ${date} 的排班`);
		}

		// 重新加载当前月份的数据
		await loadPersonalScheduleData(true);

		// 通知父组件数据已更新
		emit('schedule-updated', { type: 'single', success: true });

		return true;
	} catch (error) {
		// 保存排班失败
		ElMessage.error('排班设置失败，请检查该时间段是否已有考勤结果');
		return false;
	}
};

/**
 * 批量保存多个日期的排班
 * @param dates 日期字符串数组
 * @param shift 班组信息，null表示删除排班
 */
const saveBatchSchedule = async (dates: string[], shift: any) => {
	if (!props.personInfo?.personId || !dates.length) {
		ElMessage.error('参数不完整，无法保存排班');
		return false;
	}

	try {
		const api = attendanceApi();

		// 排序日期数组并获取开始和结束日期
		const sortedDates = dates.sort();
		const startDate = sortedDates[0];
		const endDate = sortedDates[sortedDates.length - 1];

		// 使用新的参数结构进行批量处理
		const request: any = {
			PersonIds: [props.personInfo.personId],
			ShiftType: shift ? '正常上班' : '无排班',
			DateRange: [startDate, endDate], // 使用日期范围
			AutoCalculateFuture: autoCalculateFuture.value, // 使用开关控制是否自动计算未来
			AttendanceShiftGroupId: shift ? shift.ID : null,
		};

		await api.batchChange(request);
		const successCount = dates.length;
		const totalCount = dates.length;

		// 显示结果消息
		if (shift) {
			ElMessage.success(`在 ${totalCount} 个日期安排 ${shift.Name}`);
		} else {
			ElMessage.success(`清除了 ${totalCount} 个日期的排班`);
		}

		// 重新加载当前月份的数据
		await loadPersonalScheduleData(true);
		// 通知父组件数据已更新
		emit('schedule-updated', { type: 'batch', success: true });

		return true;
	} catch (error) {
		// 批量保存排班失败
		ElMessage.error('批量排班设置失败，请检查网络连接后重试');
		return false;
	}
};

/**
 * 获取个人排班数据
 */
const loadPersonalScheduleData = async (forceReload: boolean = false) => {
	if (!props.personInfo?.personId) {
		return;
	}

	// 构建查询参数
	const year = currentDate.value.getFullYear();
	const month = currentDate.value.getMonth() + 1;
	const monthKey = `${year}-${month.toString().padStart(2, '0')}`;

	// 检查缓存：如果是同一个人员且该月份已加载过，则不重复加载（除非强制重新加载）
	const cacheKey = `${props.personInfo.personId}-${monthKey}`;
	if (!forceReload && scheduleRecordsCache.value.has(cacheKey)) {
		const cachedRecords = scheduleRecordsCache.value.get(cacheKey) || [];
		scheduleRecords.value = cachedRecords;
		return;
	}

	try {
		loading.value = true;

		const startDate = new Date(year, month - 1, 1);
		const endDate = new Date(year, month, 0);

		const searchParams: ISearchByPersonRequest = {
			PersonId: props.personInfo.personId,
			ShiftDate: [startDate.toISOString().split('T')[0], endDate.toISOString().split('T')[0]],
			Page: 1,
			Limit: 100,
		};

		// 调用API获取个人排班数据
		const api = attendanceApi();
		const response = await api.getSchedulingByPerson(searchParams);
		if (response.Data && Array.isArray(response.Data)) {
			// 处理返回的数据，提取排班记录和人员信息
			scheduleRecords.value = [];
			personInfoFromApi.value = null;

			response.Data.forEach((person: any, index: number) => {
				// 存储人员信息（取第一个人员的信息）
				if (!personInfoFromApi.value && person) {
					personInfoFromApi.value = {
						personId: person.ID || person.PersonId,
						personName: person.Name || person.PersonName,
						personCode: person.StaffNo || person.PersonCode,
						groupName: person.GroupName || person.DepartmentName || person.Department,
						// 可以添加更多字段
					};
				}

				if (person.Records && Array.isArray(person.Records)) {
					scheduleRecords.value.push(...person.Records);
				}
			});

			// 更新缓存状态
			const cacheKey = `${props.personInfo.personId}-${monthKey}`;
			scheduleRecordsCache.value.set(cacheKey, [...scheduleRecords.value]);
			lastLoadedPersonId.value = props.personInfo.personId;
		} else {
			scheduleRecords.value = [];
			// 也要缓存空数据，避免重复请求
			const cacheKey = `${props.personInfo.personId}-${monthKey}`;
			scheduleRecordsCache.value.set(cacheKey, []);
		}
	} catch (error) {
		// 获取个人排班数据失败
		scheduleRecords.value = [];
		ElMessage.error('获取个人排班数据失败，请重试');
	} finally {
		loading.value = false;
	}
};

// 加载班组数据
const loadAttendanceGroups = async () => {
	try {
		// Loading shift group data
		// 调用班组API
		const api = attendanceGroupApi().search({});
		// API instance created successfully

		// 调用获取班组列表接口
		// Calling shift group API
		const response = await api;
		// API response

		if (response && response.Data) {
			// Shift group data
			// 将班组数据转换为下拉选项格式
			attendanceGroups.value = response.Data.map((group: any) => ({
				ID: group.ID || '',
				Name: group.Name || '',
				Type: group.Type || '',
			}));
			// Converted attendance group data
			// Attendance group array length
		} else {
			// API returned empty data
			// Shift group data warning removed
		}
	} catch (error) {
		// Failed to load shift group data
		// Shift group loading error removed
	}
};

// 重新计算弹窗位置的函数
const adjustPanelPosition = () => {
	if (!showRangePanel.value) return;

	const viewportWidth = window.innerWidth;
	const viewportHeight = window.innerHeight;

	const isMobile = viewportWidth <= 768;
	const isTablet = viewportWidth > 768 && viewportWidth <= 1024;
	const isSmallScreen = viewportWidth <= 480;

	let panelWidth: number;
	if (isSmallScreen) {
		panelWidth = viewportWidth - 20;
	} else if (isMobile) {
		panelWidth = Math.min(360, viewportWidth - 40);
	} else if (isTablet) {
		panelWidth = Math.min(420, viewportWidth - 60);
	} else {
		panelWidth = 480;
	}

	const panelHeight = Math.min(600, viewportHeight - 40);
	const margin = isSmallScreen ? 10 : isMobile ? 20 : 30;

	let { x, y } = rangePanelPosition.value;

	// 重新检查水平边界
	if (x < margin) {
		x = margin;
	} else if (x + panelWidth > viewportWidth - margin) {
		x = viewportWidth - panelWidth - margin;
	}

	// 重新检查垂直边界
	if (y < margin) {
		y = margin;
	} else if (y + panelHeight > viewportHeight - margin) {
		y = viewportHeight - panelHeight - margin;
	}

	rangePanelPosition.value = { x, y };
};

// 窗口大小变化处理函数
const handleWindowResize = () => {
	adjustPanelPosition();
};

// 组件挂载时加载班组数据
onMounted(() => {
	// 确保初始化为当前日期
	const now = new Date();
	calendarKey.value = Date.now();
	currentDate.value = new Date(now);

	loadAttendanceGroups();

	// 添加窗口大小变化监听器
	window.addEventListener('resize', handleWindowResize);
});

// 组件卸载时清理监听器
onUnmounted(() => {
	window.removeEventListener('resize', handleWindowResize);
});

// 监听弹窗打开状态和人员信息变化，重新加载数据
watch(
	() => [props.visible, props.personInfo?.personId],
	([visible, personId], [oldVisible, oldPersonId]) => {
		// 人员变更时清理缓存
		if (personId !== oldPersonId) {
			scheduleRecordsCache.value.clear();
			lastLoadedPersonId.value = null;
		}

		// 只有在弹窗从关闭到打开，或者人员信息发生变化时才查询
		if (visible && personId) {
			// 弹窗从关闭变为打开
			if (oldVisible === false) {
				loadPersonalScheduleData();
			}
			// 人员信息发生变化（但弹窗已经是打开状态）
			else if (oldVisible === true && personId !== oldPersonId) {
				loadPersonalScheduleData();
			}
		}
	}
);

// 标记是否正在程序化设置日期（避免触发watch）
const isSettingDateProgrammatically = ref(false);

// 监听日期变更，重新加载数据（只有月份变化时才重新查询）
watch(currentDate, (newDate, oldDate) => {
	// 如果是程序化设置日期，不触发查询
	if (isSettingDateProgrammatically.value) {
		return;
	}

	// 只有在弹窗已打开、有人员信息、且日期确实发生变化时才查询
	// oldDate存在说明不是首次初始化
	if (props.visible && props.personInfo?.personId && oldDate && newDate.getTime() !== oldDate.getTime()) {
		// 检查是否月份发生了变化
		const newYear = newDate.getFullYear();
		const newMonth = newDate.getMonth();
		const oldYear = oldDate.getFullYear();
		const oldMonth = oldDate.getMonth();

		// 只有年份或月份发生变化时才重新查询数据
		if (newYear !== oldYear || newMonth !== oldMonth) {
			loadPersonalScheduleData(true); // 强制重新加载
		}
	}
});
</script>

<style scoped lang="scss">
/* 弹窗样式 */
.personal-schedule-dialog {
	.dialog-overlay {
		position: fixed;
		top: 0;
		left: 0;
		right: 0;
		bottom: 0;
		background: rgba(0, 0, 0, 0.5);
		display: flex;
		align-items: center;
		justify-content: center;
		z-index: 1000;
		padding: 20px;
	}

	.dialog-content {
		background: var(--calendar-bg-primary, white);
		border-radius: 12px;
		box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
		max-width: 1200px;
		width: 100%;
		max-height: 90vh;
		overflow: hidden;
		display: flex;
		flex-direction: column;
		border: 1px solid var(--calendar-border-color, #e5e7eb);
	}

	.dialog-header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 20px 24px;
		border-bottom: 1px solid var(--calendar-border-color, #e5e7eb);
		background: var(--calendar-bg-secondary, #f9fafb);

		h3 {
			margin: 0;
			font-size: 20px;
			font-weight: 600;
			color: var(--calendar-text-primary, #1f2937);
		}

		.close-btn {
			background: none;
			border: none;
			font-size: 18px;
			color: var(--calendar-text-secondary, #6b7280);
			cursor: pointer;
			padding: 8px;
			border-radius: 6px;
			transition: all 0.2s;

			&:hover {
				background: var(--calendar-bg-hover, #e5e7eb);
				color: var(--calendar-text-primary, #374151);
			}
		}
	}

	.dialog-body {
		display: flex;
		flex: 1;
		overflow: hidden;
	}
}

/* 左侧个人信息区域 */
.personal-info-section {
	width: 300px;
	padding: 24px;
	border-right: 1px solid var(--calendar-border-color, #e5e7eb);
	background: var(--calendar-bg-secondary, #f8fafc);
	display: flex;
	flex-direction: column;
	gap: 24px;
}

.person-card {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	padding: 20px;
	background: var(--calendar-bg-primary, white);
	border-radius: 12px;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
	border: 1px solid var(--calendar-border-color, #e5e7eb);

	.person-avatar {
		margin-bottom: 16px;

		.avatar-circle {
			width: 80px;
			height: 80px;
			border-radius: 50%;
			background: #409eff;
			display: flex;
			align-items: center;
			justify-content: center;
			color: white;
			font-size: 24px;
			font-weight: 600;
		}
	}

	.person-details {
		.person-name {
			margin: 0 0 16px 0;
			font-size: 20px;
			font-weight: 600;
			color: var(--calendar-text-primary, #1f2937);
		}

		.person-meta {
			display: flex;
			flex-direction: column;
			gap: 8px;

			.dept-info,
			.group-info {
				display: flex;
				flex-direction: column;
				gap: 4px;

				.label {
					font-size: 12px;
					color: var(--calendar-text-secondary, #6b7280);
					font-weight: 500;
				}

				.value {
					font-size: 14px;
					color: var(--calendar-text-primary, #374151);
				}
			}
		}
	}
}

.monthly-stats {
	padding: 20px;
	background: var(--calendar-bg-primary, white);
	border-radius: 12px;
	box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
	border: 1px solid var(--calendar-border-color, #e5e7eb);

	.stats-title {
		margin: 0 0 16px 0;
		font-size: 16px;
		font-weight: 600;
		color: var(--calendar-text-primary, #1f2937);
	}

	.stats-grid {
		display: flex;
		flex-direction: column;
		gap: 12px;

		.stat-item {
			display: flex;
			justify-content: space-between;
			align-items: center;
			padding: 8px 0;

			.stat-label {
				font-size: 14px;
				color: var(--calendar-text-secondary, #6b7280);
			}

			.stat-value {
				font-size: 16px;
				font-weight: 600;
				color: #409eff;
			}
		}
	}
}

.action-section {
	display: flex;
	flex-direction: column;
	gap: 12px;

	.primary-btn,
	.secondary-btn {
		padding: 12px 24px;
		border-radius: 8px;
		font-size: 14px;
		font-weight: 500;
		cursor: pointer;
		transition: all 0.2s;
		border: none;
	}

	.primary-btn {
		background: #409eff;
		color: white;

		&:hover {
			background: #2563eb;
		}
	}

	.secondary-btn {
		background: var(--calendar-bg-secondary, #f3f4f6);
		color: var(--calendar-text-primary, #374151);
		border: 1px solid var(--calendar-border-color, #d1d5db);

		&:hover {
			background: var(--calendar-bg-hover, #e5e7eb);
		}
	}
}

/* 自动计算未来排班开关样式 */
.auto-calculate-section {
	margin-top: 16px;
	padding: 16px;
	background: var(--calendar-bg-primary, #ffffff);
	border: 1px solid var(--calendar-border-color, #e5e7eb);
	border-radius: 8px;

	.switch-item {
		display: flex;
		align-items: center;
		justify-content: space-between;
		margin-bottom: 8px;

		.switch-label {
			font-size: 14px;
			font-weight: 500;
			color: var(--calendar-text-primary, #374151);
		}
	}

	.switch-description {
		.el-text {
			line-height: 1.4;
		}
	}
}

/* 班次选择器 */
.shift-selector {
	margin-top: 20px;
	padding-top: 20px;
	border-top: 1px solid #e5e7eb;

	.selector-title {
		margin: 0 0 16px 0;
		font-size: 16px;
		font-weight: 600;
		color: var(--calendar-text-primary, #1f2937);
	}

	.shift-list {
		display: flex;
		flex-direction: column;
		gap: 8px;
	}

	.shift-item {
		padding: 12px;
		border-radius: 8px;
		cursor: grab;
		transition: all 0.2s;
		border: 2px solid transparent;

		&:hover {
			transform: translateY(-2px);
			box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
		}

		&:active {
			cursor: grabbing;
		}

		.shift-name {
			font-weight: 600;
			margin-bottom: 4px;
		}

		.shift-time {
			font-size: 10px;
			opacity: 0.8;
			margin-bottom: 2px;
			white-space: nowrap;
			overflow: hidden;
			text-overflow: ellipsis;
		}

		.shift-type {
			font-size: 11px;
			opacity: 0.7;
		}
	}

	.mode-selector {
		margin-bottom: 12px;

		.el-radio-group {
			width: 100%;

			.el-radio-button {
				flex: 1;
			}
		}
	}

	.multi-select-info {
		margin-bottom: 16px;

		.selected-actions {
			margin-top: 8px;
			display: flex;
			gap: 8px;

			.el-button {
				flex: 1;
			}
		}
	}

	.shift-item {
		transition: all 0.2s ease;

		&.is-selected {
			transform: scale(1.02);
			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
			border: 2px solid #3b82f6 !important;
		}

		&:hover {
			transform: translateY(-1px);
			box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
		}
	}
}

/* 右侧日历区域 */
.calendar-section {
	flex: 1;
	padding: 24px;
	display: flex;
	flex-direction: column;
}

/* Element Plus 日历组件自定义样式 */
.personal-calendar {
	--el-calendar-border: 1px solid var(--calendar-border-color, #e5e7eb);
	--el-border-color: var(--calendar-border-color, #e5e7eb);
	--el-border-color-light: var(--calendar-border-color, #e5e7eb);
	--el-border-color-lighter: var(--calendar-border-color, #e5e7eb);
	border-radius: 12px;
	overflow: hidden;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
	border: 1px solid var(--calendar-border-color, #e5e7eb);

	/* 日历头部样式 */
	:deep(.el-calendar__header) {
		padding: 16px 20px;
		border-bottom: 1px solid #e5e7eb;
		background: var(--calendar-bg-secondary, #f8fafc);

		.el-calendar__title {
			font-size: 18px;
			font-weight: 600;
			color: var(--calendar-text-primary, #1f2937);
		}

		.el-button-group {
			.el-button {
				background: var(--calendar-bg-secondary, #f3f4f6);
				border-color: var(--calendar-border-color, #d1d5db);
				color: var(--calendar-text-primary, #374151);

				&:hover {
					background: var(--calendar-bg-hover, #e5e7eb);
					border-color: var(--calendar-border-color, #9ca3af);
				}
			}
		}
	}

	/* 日历主体样式 */
	:deep(.el-calendar__body) {
		padding: 0;

		.el-calendar-table {
			border: none;

			thead th {
				background: var(--calendar-bg-secondary, #f9fafb);
				border: 1px solid var(--calendar-border-color, #e5e7eb);
				padding: 12px 8px;
				font-weight: 500;
				color: var(--calendar-text-secondary, #6b7280);
				font-size: 14px;
			}

			.el-calendar-day {
				border: 1px solid var(--calendar-border-color, #e5e7eb);
				padding: 0;
				height: 120px;

				&:hover {
					background-color: var(--calendar-bg-hover, #f9fafb);
				}
			}
		}
	}
}

/* 自定义日期单元格内容样式 */
.calendar-cell-content {
	width: 100%;
	height: 100%;
	padding: 8px;
	cursor: pointer;
	display: flex;
	flex-direction: column;
	transition: all 0.2s ease;
	position: relative;

	/* 右上角班次类型角标 - 简约风格 */
	.shift-type-badge {
		position: absolute;
		top: 4px;
		right: 4px;
		font-size: 12px;
		font-weight: 600;
		padding: 3px 7px;
		border-radius: 4px;
		line-height: 1;
		z-index: 10;
		color: white;
		opacity: 0.92;

		&.badge-substitute {
			background: #06b6d4;
		}

		&.badge-exchange {
			background: #8b5cf6;
		}

		&.badge-leave {
			background: #22c55e;
		}

		&.badge-adjustment {
			background: #10b981;
		}

		&.badge-overtime {
			background: #ef4444;
		}

		&.badge-rest {
			background: #f59e0b;
		}
	}

	&:hover {
		background-color: var(--calendar-bg-hover, #f0f9ff);
	}

	&.is-today {
		background: var(--calendar-today-bg, #eff6ff);
		border: 2px solid var(--calendar-today-border, #3b82f6);
		border-radius: 4px;
	}

	&.is-weekend .day-number {
		color: #ef4444;
		font-weight: 600;
	}

	/* 异常考勤数据的样式 */
	&.has-abnormal {
		position: relative;

		&::before {
			content: '';
			position: absolute;
			top: 2px;
			right: 2px;
			width: 8px;
			height: 8px;
			background: #ef4444;
			border-radius: 50%;
			z-index: 1;
		}

		border-left: 3px solid #ef4444;
		background: rgba(239, 68, 68, 0.05);
	}

	/* 有考勤结果的日期样式 */
	&.has-attendance-result {
		border-left: 2px solid #10b981;
	}

	&.other-month {
		display: none !important;
	}

	&.drop-zone {
		border: 2px dashed #cbd5e1;

		&.drag-over {
			border-color: #3b82f6;
			background-color: #eff6ff;
		}
	}

	&.is-selected {
		background-color: #dbeafe !important;
		border: 2px solid #3b82f6;
	}

	&.is-selectable {
		cursor: pointer;

		&:hover {
			background-color: var(--calendar-bg-hover, #eff6ff);
			border: 1px solid var(--calendar-border-color, #93c5fd);
		}
	}

	// 范围选择效果
	&.is-range-selecting {
		background-color: var(--calendar-range-selecting, #f0f4f8) !important;
		border: 1px solid var(--calendar-border-color, #e2e8f0);
	}

	&.is-in-selected-range {
		background-color: var(--calendar-range-selected, #f7fafc) !important;
		border: 1px solid var(--calendar-border-color, #e2e8f0);
		position: relative;

		&::before {
			content: '';
			position: absolute;
			top: 0;
			left: 0;
			right: 0;
			bottom: 0;
			background: linear-gradient(135deg, var(--calendar-range-selecting, #f0f4f8) 0%, var(--calendar-border-color, #e2e8f0) 100%);
			opacity: 0.4;
			border-radius: 4px;
		}
	}

	&.is-range-start {
		background-color: #e2e8f0 !important;
		color: #4a5568 !important;
		border: 1px solid #cbd5e1;
		border-radius: 4px;
		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

		.day-number {
			color: #4a5568 !important;
			font-weight: 600;
		}
	}

	&.is-range-end {
		background-color: #e2e8f0 !important;
		color: #4a5568 !important;
		border: 1px solid #cbd5e1;
		border-radius: 4px;
		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

		.day-number {
			color: #4a5568 !important;
			font-weight: 600;
		}
	}

	// 单日选择（起始和结束是同一天）
	&.is-range-start.is-range-end {
		background-color: #e2e8f0 !important;
		color: #4a5568 !important;
		border: 1px solid #cbd5e1;
		border-radius: 4px;
		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
	}

	/* 状态点样式（底部） */
	.cell__dots {
		position: absolute;
		bottom: 3px;
		left: 50%;
		transform: translateX(-50%);
		display: flex;
		gap: 3px;
		z-index: 1;

		.dot {
			width: 5px;
			height: 5px;
			border-radius: 50%;
			display: inline-block;

			&.dot--leave {
				background: #16a34a; // 请假 - 绿色
			}

			&.dot--trip {
				background: #ea580c; // 出差 - 橙色
			}

			&.dot--ot {
				background: #dc2626; // 加班 - 红色
			}

			&.dot--offinlieu {
				background: #059669; // 调休 - 青绿色
			}

			&.dot--substitute {
				background: #0891b2; // 替班 - 青色
			}

			&.dot--shift-change {
				background: #7c3aed; // 换班 - 紫色
			}
		}
	}

	.day-number {
		font-size: 17px;
		font-weight: 600;
		color: #374151;
		margin-bottom: 6px;
		text-align: left;
	}

	.schedule-content {
		flex: 1;
		display: flex;
		flex-direction: column;
		justify-content: center;

		.shift-info {
			display: flex;
			flex-direction: column;
			align-items: center;
			gap: 2px;

			.shift-group {
				font-size: 13px;
				font-weight: 600;
				color: #409eff;
				line-height: 1.2;
				text-align: center;
			}

			.shift-name {
				font-size: 13px;
				font-weight: 500;
				color: #333;
				line-height: 1.2;
				text-align: center;
			}

			.shift-time {
				font-size: 9px;
				color: #666;
				line-height: 1.2;
				text-align: center;
				white-space: nowrap;
				overflow: hidden;
				text-overflow: ellipsis;
			}

			.attendance-result {
				margin-top: 2px;

				.attendance-badge {
					font-size: 9px !important;
					padding: 2px 6px !important;
					min-height: 16px;
					display: inline-flex;
					align-items: center;
				}
			}
		}

		.schedule-badge {
			padding: 4px 8px;
			border-radius: 6px;
			font-size: 13px;
			font-weight: 500;
			text-align: center;
			line-height: 1.2;

			.shift-name {
				font-weight: 600;
			}

			.schedule-type {
				font-size: 10px;
				opacity: 0.8;
				margin-top: 2px;
			}

			&.normal-shift {
				background: #dcfce7;
				color: #166534;
			}

			&.morning-shift {
				background: #fef3c7;
				color: #92400e;
			}

			&.afternoon-shift {
				background: #fed7aa;
				color: #9a3412;
			}

			&.night-shift {
				background: #e0e7ff;
				color: #3730a3;
			}

			&.rest-shift {
				background: var(--calendar-rest-bg, #f3f4f6);
				color: var(--calendar-rest-text, #6b7280);
			}

			&.leave-shift {
				background: #fecaca;
				color: #dc2626;
			}

			&.business-shift {
				background: #fef3c7;
				color: #92400e;
			}

			&.training-shift {
				background: #e0e7ff;
				color: #3730a3;
			}

			&.other-shift {
				background: var(--calendar-rest-bg, #f3f4f6);
				color: var(--calendar-rest-text, #6b7280);
			}
		}

		.no-schedule {
			display: flex;
			align-items: center;
			justify-content: center;
			flex: 1;

			.empty-text {
				font-size: 12px;
				color: #9ca3af;
			}
		}
	}
}

/* 响应式设计 */
@media (max-width: 768px) {
	.dialog-content {
		margin: 10px;
		max-height: calc(100vh - 20px);
	}

	.dialog-body {
		flex-direction: column;
	}

	.personal-info-section {
		width: 100%;
		border-right: none;
		border-bottom: 1px solid #e5e7eb;
		padding: 16px;
	}

	.calendar-section {
		padding: 16px;
	}

	/* Element Plus 日历移动端适配 */
	.personal-calendar {
		:deep(.el-calendar__header) {
			padding: 12px 16px;

			.el-calendar__title {
				font-size: 16px;
			}
		}

		:deep(.el-calendar__body) {
			.el-calendar-table {
				thead th {
					padding: 8px 4px;
					font-size: 12px;
				}

				.el-calendar-day {
					height: 80px;
				}
			}
		}
	}

	.calendar-cell-content {
		padding: 4px;

		.day-number {
			font-size: 15px;
			margin-bottom: 4px;
		}

		/* Element Plus Tag 组件移动端优化 */
		.schedule-content .schedule-badge-tag {
			font-size: 11px !important;
			padding: 1px 3px !important;
		}

		.no-schedule .empty-text {
			font-size: 11px;
		}
	}
}

/* 排班详情悬浮卡片样式 */
.card-overlay {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1999;
	background: transparent;
}

.schedule-detail-card {
	position: fixed;
	background: var(--calendar-bg-primary, white);
	border-radius: 12px;
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
	border: 1px solid var(--calendar-border-color, #e5e7eb);
	width: 280px;
	max-width: calc(100vw - 24px);
	max-height: calc(100vh - 24px);
	z-index: 2000;
	animation: cardFadeIn 0.2s ease-out;
	transform-origin: top center;
	overflow: hidden;
	display: flex;
	flex-direction: column;

	.el-card__body {
		overflow-y: auto;
		flex: 1;
		min-height: 0;
	}
}

@keyframes cardFadeIn {
	from {
		opacity: 0;
		transform: scale(0.95) translateY(-8px);
	}
	to {
		opacity: 1;
		transform: scale(1) translateY(0);
	}
}

/* Element Plus Card Header 样式优化 */
.schedule-detail-card {
	.el-card__header {
		background: #409eff;
		color: white;
		border-radius: 12px 12px 0 0;
		padding: 16px 20px;
		box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
	}

	.card-header-content {
		.date-info {
			.date-text {
				font-size: 18px;
				font-weight: 600;
				margin-right: 8px;
			}

			.weekday-text {
				font-size: 14px;
				opacity: 0.9;
			}
		}
	}
}

.card-content {
	padding: 20px;
}

.person-section {
	margin-bottom: 16px;

	.person-name {
		font-size: 16px;
		font-weight: 600;
		color: var(--calendar-text-primary, #1f2937);
		margin-bottom: 4px;
	}

	.person-code {
		font-size: 13px;
		color: #6b7280;
	}
}

.schedule-section {
	.shift-badge {
		display: inline-block;
		padding: 6px 12px;
		border-radius: 20px;
		font-size: 15px;
		font-weight: 500;
		margin-bottom: 8px;
	}

	.shift-time {
		font-size: 11px;
		color: #374151;
		font-weight: 500;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
}

.no-schedule-section {
	.no-schedule-text {
		text-align: center;
		color: #9ca3af;
		font-size: 14px;
		padding: 12px 0;
		font-style: italic;
	}
}

/* 卡片移动端适配 */
@media (max-width: 768px) {
	.schedule-detail-card {
		width: 260px;
		max-width: calc(100vw - 24px);
		max-height: calc(100vh - 24px);
	}

	.card-header {
		padding: 14px 16px;

		.card-date {
			font-size: 16px;
		}

		.card-weekday {
			font-size: 13px;
		}
	}

	.card-content {
		padding: 16px;
	}

	.person-section {
		margin-bottom: 12px;

		.person-name {
			font-size: 15px;
		}

		.person-code {
			font-size: 12px;
		}
	}

	.schedule-section {
		.shift-badge {
			padding: 5px 10px;
			font-size: 14px;
		}

		.shift-time {
			font-size: 13px;
		}
	}
}

/* 范围选择操作面板样式 */
.panel-overlay {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 1999;
	background: transparent;
}

.range-selection-panel {
	position: fixed;
	background: var(--calendar-bg-primary, white);
	border-radius: 12px;
	box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
	border: 1px solid var(--calendar-border-color, #e5e7eb);
	width: 480px;
	max-width: calc(100vw - 40px);
	max-height: calc(100vh - 40px);
	min-width: 280px;
	z-index: 2000;
	animation: panelFadeIn 0.2s ease-out;
	transform-origin: center center;
	overflow: hidden;
	display: flex;
	flex-direction: column;

	.el-card__header {
		background: #409eff;
		color: white;
		border-radius: 12px 12px 0 0;
		padding: 12px 16px;
		box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
		position: relative;

		&::after {
			content: '';
			position: absolute;
			bottom: 0;
			left: 0;
			right: 0;
			height: 1px;
			background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
		}
	}

	.el-card__body {
		padding: 16px;
		overflow-y: auto;
		flex: 1;
		display: flex;
		flex-direction: column;
	}
}

@keyframes panelFadeIn {
	from {
		opacity: 0;
		transform: scale(0.95) translateY(-8px);
	}
	to {
		opacity: 1;
		transform: scale(1) translateY(0);
	}
}

.panel-header {
	display: flex;
	align-items: center;
	justify-content: space-between;

	.range-info {
		.range-text {
			font-size: 16px;
			font-weight: 600;
		}
	}

	.panel-close-btn {
		background: none;
		border: none;
		color: white;
		font-size: 16px;
		cursor: pointer;
		padding: 4px;
		border-radius: 4px;
		transition: background-color 0.2s;

		&:hover {
			background-color: rgba(255, 255, 255, 0.2);
		}
	}
}

.panel-body {
	display: flex;
	flex-direction: column;
	gap: 16px;
	overflow-y: auto;
	flex: 1;
	min-height: 0;

	.range-form {
		flex: 1;
		overflow-y: auto;
		min-height: 0;

		.el-form-item {
			margin-bottom: 16px;

			&:last-child {
				margin-bottom: 0;
			}
		}

		.form-tip {
			margin-top: 8px;
			display: flex;
			align-items: center;
			gap: 4px;

			.el-text {
				font-size: 12px;
				line-height: 1.4;
			}
		}

		.el-radio-group {
			display: flex;
			flex-wrap: wrap;
			gap: 12px;

			.el-radio {
				margin-right: 0;
				white-space: nowrap;
			}
		}
	}

	.action-buttons {
		flex-shrink: 0;
		margin-top: auto;
		padding-top: 16px;
		border-top: 1px solid var(--calendar-border-color, #e5e7eb);
		display: flex;
		gap: 8px;
		justify-content: flex-end;

		.el-button {
			min-width: 80px;
		}
	}
}

.shift-selector-section {
	// Element Plus Select 组件样式优化
	.el-select {
		.el-input__wrapper {
			border-radius: 8px;
			border: 1px solid #d1d5db;
			transition: all 0.2s;

			&:hover {
				border-color: #9ca3af;
			}

			&.is-focus {
				border-color: #3b82f6;
				box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
			}
		}
	}
}

.range-form {
	.el-form-item {
		margin-bottom: 16px;

		&:last-child {
			margin-bottom: 0;
		}
	}

	.form-tip {
		margin-top: 8px;
		display: flex;
		align-items: center;
		gap: 4px;
	}

	.el-radio-group {
		display: flex;
		flex-wrap: wrap;
		gap: 12px;

		.el-radio {
			margin-right: 0;
		}
	}
}

.shift-option {
	display: flex;
	flex-direction: column;
	gap: 2px;

	.shift-name {
		font-weight: 500;
		color: var(--calendar-text-primary, #1f2937);
	}

	.shift-type {
		font-size: 13px;
		color: #6b7280;
	}

	.shift-time {
		font-size: 10px;
		color: #9ca3af;
		margin-top: 2px;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
}

.action-buttons {
	display: flex;
	gap: 8px;
	justify-content: center;

	.el-button {
		flex: 1;
		border-radius: 4px;
		font-weight: 500;
		transition: all 0.2s ease;
		box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

		&.el-button--primary {
			background: #e2e8f0;
			border-color: #cbd5e1;
			color: #4a5568;

			&:hover {
				background: #d1d9e0;
				border-color: #b8c5d1;
				color: #2d3748;
			}
		}

		&.el-button--danger {
			background: var(--calendar-bg-secondary, #f8fafc);
			border-color: #e2e8f0;
			color: #6b7280;

			&:hover {
				background: #f0f4f8;
				border-color: #d1d9e0;
				color: #4a5568;
			}
		}

		&.el-button--info {
			background: var(--calendar-bg-secondary, #f8fafc);
			border-color: #e5e7eb;
			color: #9ca3af;

			&:hover {
				background: #f3f4f6;
				border-color: #d1d5db;
				color: #6b7280;
			}
		}
	}
}

/* ==================== 暗黑模式适配 ==================== */
[data-theme='dark'],
.dark {
	/* 弹窗主容器暗黑模式 */
	.personal-schedule-dialog {
		.dialog-content {
			background: var(--calendar-bg-primary, #1f1f1f) !important;
			border: 1px solid var(--calendar-border-color, #424242) !important;
			box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6) !important;
		}

		.dialog-header {
			background: var(--calendar-bg-secondary, #303030) !important;
			border-bottom: 1px solid var(--calendar-border-color, #424242) !important;

			h3 {
				color: var(--calendar-text-primary, #e5eaf3) !important;
			}

			.close-btn {
				color: var(--calendar-text-secondary, #9b9da1) !important;

				&:hover {
					background: var(--calendar-bg-hover, #3c3c3c) !important;
					color: var(--calendar-text-primary, #e5eaf3) !important;
				}
			}
		}
	}

	/* 个人信息区域暗黑模式 */
	.personal-info-section {
		background: var(--calendar-bg-secondary, #303030) !important;
		border-right: 1px solid var(--calendar-border-color, #424242) !important;
	}

	/* 自动计算未来排班开关暗黑模式 */
	.auto-calculate-section {
		background: var(--calendar-bg-secondary, #303030) !important;
		border: 1px solid var(--calendar-border-color, #424242) !important;

		.switch-item .switch-label {
			color: var(--calendar-text-primary, #e5eaf3) !important;
		}
	}

	/* 操作按钮暗黑模式 */
	.action-section {
		.primary-btn {
			background: #409eff !important;
			color: white !important;

			&:hover {
				background: #66b1ff !important;
			}
		}

		.secondary-btn {
			background: var(--calendar-bg-primary, #1f1f1f) !important;
			color: var(--calendar-text-primary, #e5eaf3) !important;
			border: 1px solid var(--calendar-border-color, #424242) !important;

			&:hover {
				background: var(--calendar-bg-hover, #3c3c3c) !important;
				border-color: var(--calendar-border-color, #424242) !important;
			}
		}
	}

	.person-card {
		background: var(--calendar-bg-primary, #1f1f1f) !important;
		border: 1px solid var(--calendar-border-color, #424242) !important;
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4) !important;

		.person-details {
			.person-name {
				color: var(--calendar-text-primary, #e5eaf3) !important;
			}

			.person-meta {
				.dept-info,
				.group-info {
					.label {
						color: var(--calendar-text-secondary, #9b9da1) !important;
					}

					.value {
						color: var(--calendar-text-primary, #e5eaf3) !important;
					}
				}
			}
		}
	}

	.monthly-stats {
		background: var(--calendar-bg-primary, #1f1f1f) !important;
		border: 1px solid var(--calendar-border-color, #424242) !important;
		box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4) !important;

		.stats-title {
			color: var(--calendar-text-primary, #e5eaf3) !important;
		}

		.stats-grid {
			.stat-item {
				.stat-label {
					color: var(--calendar-text-secondary, #9b9da1) !important;
				}

				.stat-value {
					color: #409eff !important;
				}
			}
		}
	}

	/* Element Plus 日历组件暗黑模式 */
	.personal-calendar {
		--el-calendar-border: 1px solid var(--calendar-border-color, #424242) !important;
		--el-border-color: var(--calendar-border-color, #424242) !important;
		--el-border-color-light: var(--calendar-border-color, #424242) !important;
		--el-border-color-lighter: var(--calendar-border-color, #424242) !important;
		background: var(--calendar-bg-primary, #1f1f1f) !important;
		border: 1px solid var(--calendar-border-color, #424242) !important;

		/* 日历头部暗黑模式 */
		:deep(.el-calendar__header) {
			background: var(--calendar-bg-secondary, #303030) !important;
			border-bottom: 1px solid var(--calendar-border-color, #424242) !important;

			.el-calendar__title {
				color: var(--calendar-text-primary, #e5eaf3) !important;
			}

			.el-button-group .el-button {
				background: var(--calendar-bg-primary, #1f1f1f) !important;
				border-color: var(--calendar-border-color, #424242) !important;
				color: var(--calendar-text-primary, #e5eaf3) !important;

				&:hover {
					background: var(--calendar-bg-hover, #3c3c3c) !important;
					border-color: var(--calendar-border-color, #424242) !important;
				}
			}
		}

		/* 日历主体暗黑模式 */
		:deep(.el-calendar__body) {
			background: var(--calendar-bg-primary, #1f1f1f) !important;

			/* 强制覆盖所有边框 */
			* {
				border-color: var(--calendar-border-color, #424242) !important;
			}

			.el-calendar-table {
				background: var(--calendar-bg-primary, #1f1f1f) !important;
				border: 1px solid var(--calendar-border-color, #424242) !important;

				thead th {
					background: var(--calendar-bg-secondary, #303030) !important;
					border: 1px solid var(--calendar-border-color, #424242) !important;
					border-bottom: 1px solid var(--calendar-border-color, #424242) !important;
					border-right: 1px solid var(--calendar-border-color, #424242) !important;
					color: var(--calendar-text-secondary, #9b9da1) !important;
				}

				tbody tr {
					border-bottom: 1px solid var(--calendar-border-color, #424242) !important;
				}

				.el-calendar-day {
					background: var(--calendar-bg-primary, #1f1f1f) !important;
					border: 1px solid var(--calendar-border-color, #424242) !important;
					border-bottom: 1px solid var(--calendar-border-color, #424242) !important;
					border-right: 1px solid var(--calendar-border-color, #424242) !important;

					&:hover {
						background: var(--calendar-bg-hover, #3c3c3c) !important;
					}
				}

				/* 强制覆盖所有可能的边框 */
				td,
				th {
					border-color: var(--calendar-border-color, #424242) !important;
				}
			}
		}
	}

	/* 个人日历容器暗黑模式 */
	.personal-schedule-calendar {
		background: var(--calendar-bg-primary, #1f1f1f);
		color: var(--calendar-text-primary, #e5eaf3);

		/* 日历单元格内容暗黑模式 */
		.calendar-cell-content {
			/* 角标暗黑模式 - 简约风格 */
			.shift-type-badge {
				opacity: 0.95;

				&.badge-substitute {
					background: #06b6d4;
				}

				&.badge-exchange {
					background: #8b5cf6;
				}

				&.badge-leave {
					background: #22c55e;
				}

				&.badge-adjustment {
					background: #10b981;
				}

				&.badge-overtime {
					background: #ef4444;
				}

				&.badge-rest {
					background: #f59e0b;
				}
			}

			/* 状态点暗黑模式 */
			.cell__dots {
				.dot {
					&.dot--leave {
						background: #22c55e; // 请假 - 亮绿色
					}

					&.dot--trip {
						background: #f97316; // 出差 - 亮橙色
					}

					&.dot--ot {
						background: #ef4444; // 加班 - 亮红色
					}

					&.dot--offinlieu {
						background: #10b981; // 调休 - 亮青绿色
					}

					&.dot--substitute {
						background: #06b6d4; // 替班 - 亮青色
					}

					&.dot--shift-change {
						background: #a78bfa; // 换班 - 亮紫色
					}
				}
			}

			&:hover {
				background-color: var(--calendar-bg-hover, #3c3c3c) !important;
			}

			&.is-today {
				background: rgba(64, 158, 255, 0.1) !important;
				border: 2px solid rgba(64, 158, 255, 0.6) !important;
			}

			&.is-weekend .day-number {
				color: #f87171 !important;
			}

			/* 暗黑模式下的异常考勤样式 */
			&.has-abnormal {
				&::before {
					background: #f87171 !important;
				}

				border-left-color: #f87171 !important;
				background: rgba(248, 113, 113, 0.1) !important;
			}

			/* 暗黑模式下的考勤结果样式 */
			&.has-attendance-result {
				border-left-color: #34d399 !important;
			}

			&.other-month {
				display: none !important;
			}

			&.is-selected {
				background-color: rgba(64, 158, 255, 0.2) !important;
				border: 2px solid #409eff !important;
			}

			&.is-selectable:hover {
				background-color: var(--calendar-bg-hover, #3c3c3c) !important;
				border: 1px solid rgba(64, 158, 255, 0.5) !important;
			}

			&.is-range-selecting {
				background-color: rgba(64, 158, 255, 0.1) !important;
				border: 1px solid rgba(64, 158, 255, 0.3) !important;
			}

			&.is-in-selected-range {
				background-color: rgba(64, 158, 255, 0.08) !important;
				border: 1px solid rgba(64, 158, 255, 0.2) !important;
			}

			&.is-range-start,
			&.is-range-end {
				background-color: rgba(64, 158, 255, 0.25) !important;
				color: var(--calendar-text-primary, #e5eaf3) !important;
				border: 1px solid #409eff !important;

				.day-number {
					color: var(--calendar-text-primary, #e5eaf3) !important;
				}
			}

			.day-number {
				color: var(--calendar-text-primary, #e5eaf3) !important;
			}

			.schedule-content {
				.no-schedule .empty-text {
					color: var(--calendar-text-secondary, #9b9da1) !important;
				}

				.shift-info {
					.shift-group {
						color: #409eff !important;
					}

					.shift-name {
						color: var(--calendar-text-primary, #e5eaf3) !important;
					}

					.shift-time {
						color: var(--calendar-text-secondary, #9b9da1) !important;
					}
				}

				.shift-item {
					&.rest-shift {
						background: #404040 !important;
						color: #a0a0a0 !important;
					}

					&.other-shift {
						background: #404040 !important;
						color: #a0a0a0 !important;
					}
				}
			}
		}

		/* 日历网格暗黑模式 */
		.calendar-grid {
			background: var(--calendar-bg-primary, #1f1f1f);
			border: 1px solid var(--calendar-border-color, #424242);

			/* 头部星期标题 */
			.weekday-header {
				background: var(--calendar-bg-secondary, #303030);
				border-bottom: 1px solid var(--calendar-border-color, #424242);

				.weekday-cell {
					color: var(--next-text-color-regular, #e5eaf3);
					border-right: 1px solid var(--next-border-color, #424242);

					&.weekend {
						color: #f87171;
					}
				}
			}

			/* 日期单元格 */
			.calendar-row {
				border-bottom: 1px solid var(--next-border-color, #424242);
			}

			.date-cell {
				background: var(--next-bg-main-color, #1f1f1f);
				border-right: 1px solid var(--next-border-color, #424242);

				&:hover {
					background: var(--next-color-hover, #3c3c3c);
				}

				&.other-month {
					background: var(--next-color-primary, #303030);
					opacity: 0.6;
				}

				&.weekend {
					background: var(--next-color-primary, #303030);
				}

				&.today {
					background: rgba(64, 158, 255, 0.08);
					border: 2px solid rgba(64, 158, 255, 0.5);

					.date-number {
						color: #409eff !important;
						font-weight: bold;
					}
				}

				&.selected {
					background: rgba(99, 102, 241, 0.15) !important;
					border: 2px solid #6366f1 !important;
				}

				.date-number {
					color: var(--next-text-color-regular, #e5eaf3);
				}

				/* 班次信息暗黑模式 */
				.shift-info {
					.shift-item {
						background: rgba(64, 158, 255, 0.1);
						color: #93c5fd;
						border: 1px solid rgba(64, 158, 255, 0.3);

						&.normal-shift {
							background: rgba(59, 130, 246, 0.15);
							color: #93c5fd;
							border-color: rgba(59, 130, 246, 0.4);
						}

						&.leave-shift {
							background: rgba(34, 197, 94, 0.15);
							color: #86efac;
							border-color: rgba(34, 197, 94, 0.4);
						}

						&.rest-shift {
							background: rgba(245, 158, 11, 0.15);
							color: #fcd34d;
							border-color: rgba(245, 158, 11, 0.4);
						}

						&.other-shift {
							background: rgba(236, 72, 153, 0.15);
							color: #f9a8d4;
							border-color: rgba(236, 72, 153, 0.4);
						}
					}
				}
			}
		}

		/* 排班详情卡片暗黑模式 */
		.schedule-detail-card {
			background: var(--calendar-bg-primary, #1f1f1f) !important;
			border: 1px solid var(--calendar-border-color, #424242) !important;
			box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6) !important;

			:deep(.el-card__header) {
				background: #409eff !important;
				border-bottom: 1px solid var(--calendar-border-color, #424242) !important;
			}

			:deep(.el-card__body) {
				background: var(--calendar-bg-primary, #1f1f1f) !important;
			}

			:deep(.el-descriptions) {
				.el-descriptions__header {
					.el-descriptions__title {
						color: var(--calendar-text-primary, #e5eaf3) !important;
					}
				}

				.el-descriptions__body {
					.el-descriptions__table {
						.el-descriptions__cell {
							border-color: var(--calendar-border-color, #424242) !important;

							.el-descriptions__label {
								color: var(--calendar-text-secondary, #9b9da1) !important;
								background: var(--calendar-bg-secondary, #303030) !important;
							}

							.el-descriptions__content {
								color: var(--calendar-text-primary, #e5eaf3) !important;
								background: var(--calendar-bg-primary, #1f1f1f) !important;
							}
						}
					}
				}
			}

			:deep(.el-empty) {
				.el-empty__description {
					color: var(--calendar-text-secondary, #9b9da1) !important;
				}
			}
		}

		/* 范围选择面板暗黑模式 */
		.range-selection-panel {
			background: var(--calendar-bg-primary, #1f1f1f) !important;
			border: 1px solid var(--calendar-border-color, #424242) !important;
			box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6) !important;

			:deep(.el-card__header) {
				background: #409eff !important;
				border-bottom: 1px solid var(--calendar-border-color, #424242) !important;
			}

			:deep(.el-card__body) {
				background: var(--calendar-bg-primary, #1f1f1f) !important;
			}

			.panel-header {
				background: var(--calendar-bg-secondary, #303030);
				border-bottom: 1px solid var(--calendar-border-color, #424242);

				.range-text {
					color: var(--calendar-text-primary, #e5eaf3) !important;
				}

				.panel-close-btn {
					color: var(--calendar-text-secondary, #9b9da1) !important;

					&:hover {
						color: var(--calendar-text-primary, #e5eaf3) !important;
						background: var(--calendar-bg-hover, #3c3c3c) !important;
					}
				}
			}

			.panel-body {
				background: var(--calendar-bg-primary, #1f1f1f) !important;

				/* Element Plus Select 组件暗黑模式覆盖 */
				:deep(.el-select) {
					.el-input__wrapper {
						background: var(--calendar-bg-secondary, #303030) !important;
						border-color: var(--calendar-border-color, #424242) !important;

						&:hover {
							border-color: var(--calendar-border-color, #424242) !important;
						}

						&.is-focus {
							border-color: #409eff !important;
						}

						.el-input__inner {
							color: var(--calendar-text-primary, #e5eaf3) !important;

							&::placeholder {
								color: var(--calendar-text-secondary, #9b9da1) !important;
							}
						}
					}
				}

				/* Element Plus Button 组件暗黑模式覆盖 */
				:deep(.el-button) {
					&.el-button--primary {
						background: #409eff !important;
						border-color: #409eff !important;

						&:hover {
							background: #66b1ff !important;
							border-color: #66b1ff !important;
						}
					}

					&.el-button--default {
						background: var(--calendar-bg-secondary, #303030) !important;
						border-color: var(--calendar-border-color, #424242) !important;
						color: var(--calendar-text-primary, #e5eaf3) !important;

						&:hover {
							background: var(--calendar-bg-hover, #3c3c3c) !important;
							border-color: var(--calendar-border-color, #424242) !important;
						}
					}

					&.el-button--danger {
						background: #f56c6c !important;
						border-color: #f56c6c !important;

						&:hover {
							background: #f78989 !important;
							border-color: #f78989 !important;
						}
					}

					&.el-button--info {
						background: var(--calendar-bg-tertiary, #404040) !important;
						border-color: var(--calendar-border-color, #424242) !important;
						color: var(--calendar-text-primary, #e5eaf3) !important;

						&:hover {
							background: var(--calendar-bg-hover, #3c3c3c) !important;
							border-color: var(--calendar-border-color, #424242) !important;
						}
					}
				}
			}
		}

		/* 工具提示暗黑模式 */
		.tooltip-content {
			background: rgba(31, 31, 31, 0.95) !important;
			color: var(--next-text-color-regular, #e5eaf3) !important;
			border: 1px solid var(--next-border-color, #424242) !important;
			box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5) !important;

			.tooltip-title {
				color: var(--next-text-color-regular, #e5eaf3);
				border-bottom: 1px solid var(--next-border-color, #424242);
			}

			.tooltip-body {
				.shift-detail {
					background: var(--next-color-primary, #303030);
					border: 1px solid var(--next-border-color, #424242);

					.detail-label {
						color: var(--next-text-color-placeholder, #9b9da1);
					}

					.detail-value {
						color: var(--next-text-color-regular, #e5eaf3);
					}
				}
			}
		}
	}
}

/* 平板适配 */
@media (max-width: 1024px) and (min-width: 769px) {
	.range-selection-panel {
		width: 420px;
		max-width: calc(100vw - 60px);
	}
}

/* 移动端适配 */
@media (max-width: 768px) {
	.range-selection-panel {
		width: calc(100vw - 40px);
		max-width: 360px;
		max-height: calc(100vh - 40px);
		min-width: 280px;

		.el-card__header {
			padding: 10px 12px;
		}

		.el-card__body {
			padding: 12px;
		}
	}

	.panel-header {
		.range-text {
			font-size: 14px;
		}

		.panel-close-btn {
			font-size: 14px;
		}
	}

	.panel-body {
		gap: 12px;

		.range-form {
			.el-form-item {
				margin-bottom: 12px;
			}

			.el-radio-group {
				gap: 8px;
			}
		}
	}

	.action-buttons {
		gap: 8px;
		flex-wrap: wrap;

		.el-button {
			padding: 8px 12px;
			font-size: 13px;
			flex: 1;
			min-width: 80px;
		}
	}
}

/* 小屏幕手机适配 */
@media (max-width: 480px) {
	.schedule-detail-card {
		width: calc(100vw - 24px);
		max-width: calc(100vw - 24px);
		max-height: calc(100vh - 24px);

		.el-card__header {
			padding: 12px 14px;
		}

		.el-card__body {
			padding: 14px;
		}
	}

	.range-selection-panel {
		width: calc(100vw - 20px);
		max-width: 100%;
		max-height: calc(100vh - 20px);
		border-radius: 8px;
		top: 10px !important;
		left: 10px !important;
		right: 10px;
		bottom: 10px;
		transform: none !important;
	}

	.panel-header {
		padding: 8px 10px;

		.range-text {
			font-size: 13px;
		}
	}

	.panel-body {
		padding: 12px 10px;
		gap: 10px;

		.range-form {
			.el-form-item {
				margin-bottom: 10px;

				:deep(.el-form-item__label) {
					font-size: 13px;
					padding-bottom: 4px;
				}
			}

			.el-radio-group {
				gap: 6px;

				.el-radio {
					font-size: 13px;
				}
			}
		}
	}

	.action-buttons {
		gap: 6px;
		padding-top: 12px;

		.el-button {
			padding: 10px 8px;
			font-size: 12px;
		}
	}

	/* 暗黑模式下的移动端调整 */
	[data-theme='dark'],
	.dark {
		.personal-schedule-calendar {
			.date-cell {
				.shift-info {
					.shift-item {
						font-size: 11px;
						padding: 2px 4px;
					}
				}
			}
		}
	}
}

/* 实际打卡时间样式 */
.punch-time-info {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.punch-time-item {
	display: flex;
	align-items: center;
	gap: 4px;

	.el-text {
		display: flex;
		align-items: center;
		gap: 4px;
	}

	.el-icon {
		font-size: 14px;
	}
}
</style>
